@koda-sl/baker-cli 0.394.0-dev.d28cef496 → 0.395.0-dev.f8ecbaa42
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 +99 -48
- package/canvas/static-ad-composition/index.html +404 -0
- package/canvas/static-ad-composition/meta.json +53 -0
- package/dist/{chunk-FEJQJ64L.js → chunk-Q3LMLQAI.js} +399 -119
- package/dist/chunk-Q3LMLQAI.js.map +1 -0
- package/dist/cli.js +5971 -3049
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.d.ts +6 -0
- package/dist/engine/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-FEJQJ64L.js.map +0 -1
package/README.md
CHANGED
|
@@ -110,6 +110,12 @@ question — and nothing in the output said so. Both spellings of an option work
|
|
|
110
110
|
that lists every option: `baker schema <command>` (`check_schema`) when the command
|
|
111
111
|
registers a schema, `baker <command> --help` (`check_help`) when it does not.
|
|
112
112
|
|
|
113
|
+
### Unknown commands are refused
|
|
114
|
+
|
|
115
|
+
A subcommand that does not exist (say, `scaffold` under `baker canvas`) stops with `UNKNOWN_COMMAND`
|
|
116
|
+
and exit code 1, naming the real commands closest to it and every command the parent
|
|
117
|
+
has, instead of printing the parent's help screen. Nothing runs.
|
|
118
|
+
|
|
113
119
|
### Narrowing a result: `--fields`
|
|
114
120
|
|
|
115
121
|
`--fields a,b,c` works on every command that prints the JSON envelope. It keeps only
|
|
@@ -3830,7 +3836,7 @@ Starting a batch returns 202 + a `generationId`; the CLI polls until the takes s
|
|
|
3830
3836
|
```bash
|
|
3831
3837
|
baker studio generate "a friendly golden retriever in a bright modern living room" --aspect-ratio 16:9
|
|
3832
3838
|
baker studio generate "hero shot of a matte black water bottle on wet marble, studio light" \
|
|
3833
|
-
--model google/gemini-3-pro-image
|
|
3839
|
+
--model google/gemini-3-pro-image --image-size 2K --aspect-ratio 4:5 --count 3
|
|
3834
3840
|
baker studio generate "home office hero, warm minimalist, natural light, 35mm photo" \
|
|
3835
3841
|
--reference "https://i.pinimg.com/…/pin1.jpg,./src/brand/logos/product.png,j57abc123def456ghi789"
|
|
3836
3842
|
baker studio generate "founder-style selfie, kitchen background" --skill ugc-selfie-hook
|
|
@@ -3838,13 +3844,13 @@ baker studio generate "flat geometric mascot, brand palette" \
|
|
|
3838
3844
|
--model recraft/recraft-v4.1-pro-vector --rgb-colors "[[10,10,10],[255,80,0]]" --bg-rgb "[255,255,255]"
|
|
3839
3845
|
```
|
|
3840
3846
|
|
|
3841
|
-
**Models** (`--model`, default `google/gemini-3.1-flash-image
|
|
3847
|
+
**Models** (`--model`, default `google/gemini-3.1-flash-image`):
|
|
3842
3848
|
|
|
3843
3849
|
| Model | Best for | Aspect ratios | Sizes |
|
|
3844
3850
|
|---|---|---|---|
|
|
3845
|
-
| `google/gemini-3.1-flash-image
|
|
3851
|
+
| `google/gemini-3.1-flash-image` **(default)** | Fast iteration, reference editing, extreme aspect ratios (Nano Banana flash) | standard **+** `1:4` `4:1` `1:8` `8:1` | `0.5K`–`4K` |
|
|
3846
3852
|
| `google/gemini-3.1-flash-lite-image` | Cheapest tier (Nano Banana 2 Lite) — half the flash price per output token. High-volume variants and any 1K-sufficient frame, portrait ad formats included | standard **+** `1:4` `4:1` `1:8` `8:1` | `1K` **only** |
|
|
3847
|
-
| `google/gemini-3-pro-image
|
|
3853
|
+
| `google/gemini-3-pro-image` | Highest fidelity (Nano Banana Pro) — strong prompt adherence and reference grounding | standard set | `1K` `2K` `4K` |
|
|
3848
3854
|
| `openai/gpt-image-2` | Photoreal + cleanest in-image text — ad/landing reproduction | standard set **minus** `4:5` `5:4` | *(none — derived from the ratio)* |
|
|
3849
3855
|
| `openai/gpt-5.4-image-2` | **Legacy** — accepted so canvases authored before the gpt-image-2 switch still re-run. Don't pick it for new work. | standard set | `1K` `2K` `4K` (ignored upstream) |
|
|
3850
3856
|
| `recraft/recraft-v4.1-pro-vector` | Vector/flat/SVG-style with palette control. Writes **SVG**, so its output cannot be fed back as a reference | `1:1` `4:3` `3:4` `16:9` `9:16` **only** | *(none — refuses `image_size`)* |
|
|
@@ -3853,7 +3859,7 @@ Standard aspect ratios: `1:1` `2:3` `3:2` `3:4` `4:3` `4:5` `5:4` `9:16` `16:9`
|
|
|
3853
3859
|
|
|
3854
3860
|
`openai/gpt-image-2` accepts neither `image_size` nor the `4:5` / `5:4` portrait ratios — OpenAI picks the pixel dimensions from the ratio. To ship a 4:5 Meta feed ad from it, render the hero at `3:4` and adapt with `image_aspect_adapt`; the scaffolders do this for you.
|
|
3855
3861
|
|
|
3856
|
-
`google/gemini-3.1-flash-lite-image` renders at `1K` and nothing else — `2K` / `4K` is a provider 400 (`resolution: not supported. Accepted: 1K`), not a soft clamp. It costs half of `google/gemini-3.1-flash-image
|
|
3862
|
+
`google/gemini-3.1-flash-lite-image` renders at `1K` and nothing else — `2K` / `4K` is a provider 400 (`resolution: not supported. Accepted: 1K`), not a soft clamp. It costs half of `google/gemini-3.1-flash-image` per output image token ($0.00003 vs $0.00006), so it is the cheap-iteration tier for images. Keep hero stills and reference sheets that need `2K`+ on the flash or pro tier.
|
|
3857
3863
|
|
|
3858
3864
|
`recraft/recraft-v4.1-pro-vector` is the narrowest of the roster in three ways, all read off its live endpoint schema: it renders **five** ratios (no `4:5` / `5:4` / `2:3` / `3:2` / `21:9`, so a portrait ad shape snaps to `3:4`), it has **no `image_size`** — the provider accepts one and ignores it, which is why it is refused here — and it takes **exactly one** reference image against 14-16 elsewhere. Its output is an SVG, and the image models read only JPG, PNG and WebP, so a Recraft take cannot be used as a reference for the next render.
|
|
3859
3865
|
|
|
@@ -3863,7 +3869,7 @@ A knob the chosen model does not advertise is **refused, not silently dropped**
|
|
|
3863
3869
|
|
|
3864
3870
|
| Flag | Description |
|
|
3865
3871
|
|---|---|
|
|
3866
|
-
| `--model` | Model id (default `google/gemini-3.1-flash-image
|
|
3872
|
+
| `--model` | Model id (default `google/gemini-3.1-flash-image`) |
|
|
3867
3873
|
| `--aspect-ratio` | Output aspect ratio (default `1:1`) |
|
|
3868
3874
|
| `--count` | Takes to render, 1–8 (default 1) |
|
|
3869
3875
|
| `--reference` | Comma-separated visual references, in order: **library image ids**, **local file paths**, and/or **image URLs**. Paths and URLs are added to the media library first and passed as ids — the studio takes library ids only, so the client can see what grounded the render and the reference stays reusable. The biggest quality lever there is. |
|
|
@@ -3971,7 +3977,7 @@ Compact output per take is `{ slot, status, imageId | videoId, url, inLibrary, e
|
|
|
3971
3977
|
| `lifestyle-scene` | `openai/gpt-image-2` | `4:5` | 3 | **yes** |
|
|
3972
3978
|
| `cinematic-still` | `google/gemini-3-pro-image-preview` | `16:9` | 3 | no |
|
|
3973
3979
|
| `brand-icon` | `recraft/recraft-v4.1-pro-vector` | `1:1` | 4 | no |
|
|
3974
|
-
| `background-plate` | `google/gemini-3.1-flash-image
|
|
3980
|
+
| `background-plate` | `google/gemini-3.1-flash-image` | `4:5` | 3 | no |
|
|
3975
3981
|
|
|
3976
3982
|
A skill fills only what you did **not** pin — `--model`, `--aspect-ratio` and `--count` always win. Which means a skill can change the model, and the model decides what the rest of the flags may be: `--skill brand-icon` lands on Recraft, so it takes one reference image rather than fourteen, refuses `--image-size`, and writes SVG that cannot be fed back in as a reference. The CLI resolves the skill's model before checking the reference count, so an over-long `--reference` list is refused before anything is uploaded.
|
|
3977
3983
|
|
|
@@ -4060,8 +4066,37 @@ baker canvas catalog --node video_generate
|
|
|
4060
4066
|
# multi-line / accented copy; the edit is re-validated before it is written back)
|
|
4061
4067
|
baker canvas set-prompt my-canvas.json s0_start --text "Close-up, flushed and breathless"
|
|
4062
4068
|
baker canvas set-prompt my-canvas.json s0_start --text-file ./hook-frame.txt
|
|
4069
|
+
|
|
4070
|
+
# 6. Set a static ad's words without editing prompt.json by hand. No flags lists
|
|
4071
|
+
# every line with its role and layout region; flags change them in the copy
|
|
4072
|
+
# and the layout together and keep everything else in prompt.json.
|
|
4073
|
+
baker canvas copy spring-offer
|
|
4074
|
+
baker canvas copy spring-offer --headline "Nine days, not 31." --cta "Book your free survey" \
|
|
4075
|
+
--line body="Scaffolding included" --line body="MCS certificate included" \
|
|
4076
|
+
--line eyebrow@header="ROOFTOP SOLAR" --treatment cta=pill --remove caption --remove-logo
|
|
4063
4077
|
```
|
|
4064
4078
|
|
|
4079
|
+
`baker canvas copy <slug>` flags:
|
|
4080
|
+
|
|
4081
|
+
| Flag | What it does |
|
|
4082
|
+
|---|---|
|
|
4083
|
+
| `--headline`, `--sub`, `--cta` | Shorthand for `--line headline=…`, `--line subheadline=…`, `--line cta=…` |
|
|
4084
|
+
| `--line <role>[@<region>]=<text>` | Repeatable. A role you name gets exactly the lines you give, in order; old extra ones are dropped. A new line goes to the region of its role's last line, or the region you name. Roles: `headline`, `subheadline`, `body`, `cta`, `caption`, `fine_print`, `eyebrow` |
|
|
4085
|
+
| `--remove <role>` | Repeatable. Drops every line of that role |
|
|
4086
|
+
| `--treatment <role>=<kind>` | `plain`, `underline`, `arrow`, `button`, `pill`, `sticker`, `highlight` |
|
|
4087
|
+
| `--color <role>=<#rrggbb>` | The ink of that role's lines |
|
|
4088
|
+
| `--remove-logo` | Draws the ad without a logo |
|
|
4089
|
+
| `--mechanism "<verbal\|visual\|structural>: <device>"` | Repeatable. Replaces the reference's winning mechanisms with ours |
|
|
4090
|
+
| `--alternate "<hook>"` | Repeatable. Records a hook alternate on the creative's `_definition.md` |
|
|
4091
|
+
| `--spec <file.json>` | The same fields as JSON, for long copy |
|
|
4092
|
+
| `--dry-run` | Prints the changes without writing |
|
|
4093
|
+
|
|
4094
|
+
It refuses, before writing anything, a role it does not know, a region the layout does not have, a new
|
|
4095
|
+
line with no region to go to, and copy that says the same line twice. Replacing the headline that is
|
|
4096
|
+
the creative's recorded `hook` keeps the old one in `hookAlternates` and makes the new one the hook.
|
|
4097
|
+
The output lists each change, the resulting copy, what was recorded, and the next command
|
|
4098
|
+
(`baker canvas run <slug> --draft`).
|
|
4099
|
+
|
|
4065
4100
|
#### The brand, the ending and the cast are drawn, not generated
|
|
4066
4101
|
|
|
4067
4102
|
`baker canvas scaffold-ad` renders everything with a known, fixed appearance locally,
|
|
@@ -4163,7 +4198,7 @@ A re-run with no changes hits the cache for every node — total runtime drops t
|
|
|
4163
4198
|
"type": "text_generate",
|
|
4164
4199
|
"inputs": { "topic": "$ref:topic.text" },
|
|
4165
4200
|
"params": {
|
|
4166
|
-
"model": "google/gemini-3.1-flash-image
|
|
4201
|
+
"model": "google/gemini-3.1-flash-image",
|
|
4167
4202
|
"prompt": "Write a 6-word tagline for {{topic}}."
|
|
4168
4203
|
}
|
|
4169
4204
|
}
|
|
@@ -4345,7 +4380,7 @@ Example pipeline:
|
|
|
4345
4380
|
{ "id": "font", "type": "ingest", "params": { "source": "url", "url": "https://cdn.example.com/brand.woff2", "expect": "font" } }
|
|
4346
4381
|
{ "id": "specimen", "type": "font_specimen", "inputs": { "font": "$ref:font.asset" }, "params": {} }
|
|
4347
4382
|
{ "id": "gen", "type": "image_generate", "inputs": { "reference": "$ref:specimen.image" },
|
|
4348
|
-
"params": { "model": "google/gemini-3.1-flash-image
|
|
4383
|
+
"params": { "model": "google/gemini-3.1-flash-image", "prompt": "Launch poster headlined 'SALE ENDS FRIDAY' using the exact typeface from the reference image" } }
|
|
4349
4384
|
```
|
|
4350
4385
|
|
|
4351
4386
|
---
|
|
@@ -4484,13 +4519,13 @@ No `image_size`: OpenAI derives the pixel dimensions from the ratio, so a size i
|
|
|
4484
4519
|
|
|
4485
4520
|
Kept only so canvases authored before the gpt-image-2 switch still validate and re-run unchanged — `baker canvas rerun` replays a recorded canvas verbatim. It keeps the full standard ratio set (including `4:5` / `5:4`) and still accepts `image_size`, which the provider ignores. Prefer `openai/gpt-image-2` for anything new.
|
|
4486
4521
|
|
|
4487
|
-
###### Model: `google/gemini-3.1-flash-image-preview`
|
|
4522
|
+
###### Model: `google/gemini-3.1-flash-image` (legacy id `google/gemini-3.1-flash-image-preview` still accepted)
|
|
4488
4523
|
|
|
4489
4524
|
Gemini flash image model — the default. Fast iteration, reference editing, extreme aspect ratios, and the `0.5K` size for cheap previews.
|
|
4490
4525
|
|
|
4491
4526
|
| Name | Type | Required | Notes |
|
|
4492
4527
|
|---|---|---|---|
|
|
4493
|
-
| `model` | literal | yes | `"google/gemini-3.1-flash-image
|
|
4528
|
+
| `model` | literal | yes | `"google/gemini-3.1-flash-image"` |
|
|
4494
4529
|
| `prompt` | string | yes | non-empty |
|
|
4495
4530
|
| `aspect_ratio` | enum | no | EXTREME AR |
|
|
4496
4531
|
| `image_size` | enum | no | `0.5K \| 1K \| 2K \| 4K` |
|
|
@@ -4506,20 +4541,20 @@ Gemini flash image model — the default. Fast iteration, reference editing, ext
|
|
|
4506
4541
|
| `aspect_ratio` | enum | no | EXTREME AR — keeps `4:5` / `5:4`, so Meta/Instagram portrait formats come straight out of it |
|
|
4507
4542
|
| `image_size` | enum | no | `1K` only — `2K` / `4K` is a hard provider 400 (`resolution: not supported. Accepted: 1K`), so `validate` rejects it before the call costs anything |
|
|
4508
4543
|
|
|
4509
|
-
Reach for it for high-volume variants and any frame `1K` covers. Send hero stills and the `image_reference_sheet` subject sheet — anything that needs `2K`+ — to `google/gemini-3.1-flash-image
|
|
4544
|
+
Reach for it for high-volume variants and any frame `1K` covers. Send hero stills and the `image_reference_sheet` subject sheet — anything that needs `2K`+ — to `google/gemini-3.1-flash-image` or `google/gemini-3-pro-image`. Registered for `image_generate` only; `image_reference_sheet` and `image_aspect_adapt` do not accept it.
|
|
4510
4545
|
|
|
4511
4546
|
```json
|
|
4512
4547
|
{ "id": "variant_a", "type": "image_generate",
|
|
4513
4548
|
"params": { "model": "google/gemini-3.1-flash-lite-image", "prompt": "Portrait feed ad for a citrus energy drink …", "aspect_ratio": "4:5", "image_size": "1K" } }
|
|
4514
4549
|
```
|
|
4515
4550
|
|
|
4516
|
-
###### Model: `google/gemini-3-pro-image-preview`
|
|
4551
|
+
###### Model: `google/gemini-3-pro-image` (legacy id `google/gemini-3-pro-image-preview` still accepted)
|
|
4517
4552
|
|
|
4518
4553
|
"Nano Banana Pro" — heavier, higher-quality Gemini.
|
|
4519
4554
|
|
|
4520
4555
|
| Name | Type | Required | Notes |
|
|
4521
4556
|
|---|---|---|---|
|
|
4522
|
-
| `model` | literal | yes | `"google/gemini-3-pro-image
|
|
4557
|
+
| `model` | literal | yes | `"google/gemini-3-pro-image"` |
|
|
4523
4558
|
| `prompt` | string | yes | non-empty |
|
|
4524
4559
|
| `aspect_ratio` | enum | no | STD AR |
|
|
4525
4560
|
| `image_size` | enum | no | `1K \| 2K \| 4K` |
|
|
@@ -4563,7 +4598,7 @@ The sheet prompt is baked into the backend — there is no `prompt` param. `subj
|
|
|
4563
4598
|
|
|
4564
4599
|
| Name | Type | Required | Notes |
|
|
4565
4600
|
|---|---|---|---|
|
|
4566
|
-
| `model` | enum | yes | one of `google/gemini-3-pro-image
|
|
4601
|
+
| `model` | enum | yes | one of `google/gemini-3-pro-image`, `google/gemini-3.1-flash-image` (their `-preview` ids still accepted) |
|
|
4567
4602
|
| `subject_description` | string | yes | exact subject wording — reuse verbatim downstream |
|
|
4568
4603
|
| `subject_type` | enum | yes | `character \| person \| product` — picks the template + default views |
|
|
4569
4604
|
| `views` | string[] | no | 2–6 view labels; defaults per `subject_type` (below) |
|
|
@@ -4590,7 +4625,7 @@ Default views per `subject_type`:
|
|
|
4590
4625
|
|
|
4591
4626
|
Guidance:
|
|
4592
4627
|
- 3–4 full-body views at `16:9` is the sweet spot for characters; more views shrink each figure and lose face detail.
|
|
4593
|
-
- Use `google/gemini-3.1-flash-image
|
|
4628
|
+
- Use `google/gemini-3.1-flash-image` while iterating; switch to `google/gemini-3-pro-image` at `2K`+ for final 6-view sheets.
|
|
4594
4629
|
- Feed the whole sheet downstream as one `reference` input — don't crop individual views.
|
|
4595
4630
|
|
|
4596
4631
|
**Example** — two photos of a sneaker → product sheet → consistent hero shot:
|
|
@@ -4603,14 +4638,14 @@ Guidance:
|
|
|
4603
4638
|
{ "id": "b", "type": "ingest", "params": { "source": "url", "url": "https://example.com/sneaker-side.jpg", "expect": "image" } },
|
|
4604
4639
|
{ "id": "sheet", "type": "image_reference_sheet",
|
|
4605
4640
|
"inputs": { "references": ["$ref:a.asset", "$ref:b.asset"] },
|
|
4606
|
-
"params": { "model": "google/gemini-3-pro-image
|
|
4641
|
+
"params": { "model": "google/gemini-3-pro-image",
|
|
4607
4642
|
"subject_description": "navy leather low-top sneaker with white sole and gold logo",
|
|
4608
4643
|
"subject_type": "product",
|
|
4609
4644
|
"style": "matte black studio background, soft even key light",
|
|
4610
4645
|
"aspect_ratio": "16:9", "image_size": "2K" } },
|
|
4611
4646
|
{ "id": "hero", "type": "image_generate",
|
|
4612
4647
|
"inputs": { "reference": "$ref:sheet.sheet" },
|
|
4613
|
-
"params": { "model": "google/gemini-3-pro-image
|
|
4648
|
+
"params": { "model": "google/gemini-3-pro-image",
|
|
4614
4649
|
"prompt": "The navy leather low-top sneaker with white sole and gold logo from the reference sheet, hero shot on a rain-soaked neon street at night" } }
|
|
4615
4650
|
],
|
|
4616
4651
|
"output": { "node": "hero", "output": "images" }
|
|
@@ -4635,7 +4670,7 @@ The adaptation prompt is baked into the backend and is direction-aware (a wider
|
|
|
4635
4670
|
|
|
4636
4671
|
| Name | Type | Required | Notes |
|
|
4637
4672
|
|---|---|---|---|
|
|
4638
|
-
| `model` | enum | yes | one of `google/gemini-3-pro-image
|
|
4673
|
+
| `model` | enum | yes | one of `google/gemini-3-pro-image`, `google/gemini-3.1-flash-image` (their `-preview` ids still accepted) |
|
|
4639
4674
|
| `formats` | enum[] | one of `formats`/`platform` | 1–6 unique target ratios from `1:1 \| 2:3 \| 3:2 \| 3:4 \| 4:3 \| 4:5 \| 5:4 \| 9:16 \| 16:9 \| 21:9` — output order follows this list |
|
|
4640
4675
|
| `platform` | enum | one of `formats`/`platform` | a placement preset that expands to that platform's canonical on-grid set: `meta_feed` (4:5), `meta_feed_stories` (4:5, 9:16), `meta_all` (4:5, 9:16, 1:1), `linkedin` (1:1), `google_demandgen` (1:1, 4:5, 16:9), `tiktok` (9:16), `youtube` (16:9). Mutually exclusive with `formats`. Off-grid platform targets (e.g. Google 1.91:1) are not rendered here — crop them from the nearest rendered ratio with `baker images crop`. |
|
|
4641
4676
|
| `guidance` | string | no | hints appended to the baked prompt, e.g. `"keep the CTA button fully visible"` |
|
|
@@ -4652,7 +4687,7 @@ The adaptation prompt is baked into the backend and is direction-aware (a wider
|
|
|
4652
4687
|
Guidance:
|
|
4653
4688
|
- Reach for `platform` when you want a placement's whole set without naming ratios (e.g. `"platform": "meta_feed_stories"`); use `formats` when you need an exact, custom ratio list. Set one, never both.
|
|
4654
4689
|
- List the source's own ratio in `formats` too when you want the full set in one output — it passes through for free.
|
|
4655
|
-
- Use `google/gemini-3.1-flash-image
|
|
4690
|
+
- Use `google/gemini-3.1-flash-image` while iterating; switch to `google/gemini-3-pro-image` for final-quality adaptation.
|
|
4656
4691
|
- Indexes are positional: `$ref:adapt.images#1` is always the second entry of `formats`.
|
|
4657
4692
|
|
|
4658
4693
|
**Example** — one 9:16 story creative fanned out to feed and landscape:
|
|
@@ -4662,12 +4697,12 @@ Guidance:
|
|
|
4662
4697
|
"schema": "baker-canvas/1",
|
|
4663
4698
|
"nodes": [
|
|
4664
4699
|
{ "id": "hero", "type": "image_generate",
|
|
4665
|
-
"params": { "model": "google/gemini-3-pro-image
|
|
4700
|
+
"params": { "model": "google/gemini-3-pro-image",
|
|
4666
4701
|
"prompt": "Story-format ad for a citrus energy drink: can centered, bold OFFER text top, logo bottom",
|
|
4667
4702
|
"aspect_ratio": "9:16", "image_size": "2K" } },
|
|
4668
4703
|
{ "id": "adapt", "type": "image_aspect_adapt",
|
|
4669
4704
|
"inputs": { "source": "$ref:hero.images#0" },
|
|
4670
|
-
"params": { "model": "google/gemini-3-pro-image
|
|
4705
|
+
"params": { "model": "google/gemini-3-pro-image",
|
|
4671
4706
|
"formats": ["9:16", "1:1", "16:9"],
|
|
4672
4707
|
"guidance": "keep the offer text and logo fully visible" } }
|
|
4673
4708
|
],
|
|
@@ -4802,9 +4837,9 @@ Fail-fast: an invalid model selection (wrong count, duplicate or out-of-range pi
|
|
|
4802
4837
|
{
|
|
4803
4838
|
"schema": "baker-canvas/1",
|
|
4804
4839
|
"nodes": [
|
|
4805
|
-
{ "id": "a", "type": "image_generate", "params": { "model": "google/gemini-3.1-flash-image
|
|
4806
|
-
{ "id": "b", "type": "image_generate", "params": { "model": "google/gemini-3.1-flash-image
|
|
4807
|
-
{ "id": "c", "type": "image_generate", "params": { "model": "google/gemini-3.1-flash-image
|
|
4840
|
+
{ "id": "a", "type": "image_generate", "params": { "model": "google/gemini-3.1-flash-image", "prompt": "hero shot of a ceramic mug, warm morning light" } },
|
|
4841
|
+
{ "id": "b", "type": "image_generate", "params": { "model": "google/gemini-3.1-flash-image", "prompt": "hero shot of a ceramic mug, studio softbox" } },
|
|
4842
|
+
{ "id": "c", "type": "image_generate", "params": { "model": "google/gemini-3.1-flash-image", "prompt": "hero shot of a ceramic mug, dramatic side light" } },
|
|
4808
4843
|
{ "id": "best", "type": "image_select",
|
|
4809
4844
|
"inputs": { "images": ["$ref:a.images#0", "$ref:b.images#0", "$ref:c.images#0"] },
|
|
4810
4845
|
"params": { "model": "~google/gemini-pro-latest", "prompt": "most premium-feeling lighting, no harsh shadows" } }
|
|
@@ -5432,9 +5467,9 @@ Cache policies (via `--cache-policy`):
|
|
|
5432
5467
|
|
|
5433
5468
|
### CLI commands
|
|
5434
5469
|
|
|
5435
|
-
#### `baker canvas validate <file.json>`
|
|
5470
|
+
#### `baker canvas validate <slug|file.json>`
|
|
5436
5471
|
|
|
5437
|
-
Parse + schema-check + per-node deep validation (composition `meta.json` is consulted for `hyperframe_*`). Prints a structured result with a per-node cost preview. Exits non-zero on issues. Never calls Convex, never spends credits.
|
|
5472
|
+
Takes a creative's slug (`baker canvas validate <slug>` reads `src/creatives/<slug>/<slug>.canvas.json`) or a canvas path; a slug that names no creative fails with `not_found` and lists the creatives there are. Parse + schema-check + per-node deep validation (composition `meta.json` is consulted for `hyperframe_*`). Prints a structured result with a per-node cost preview. Exits non-zero on issues. Never calls Convex, never spends credits.
|
|
5438
5473
|
|
|
5439
5474
|
```bash
|
|
5440
5475
|
$ baker canvas validate my-canvas.json
|
|
@@ -5474,9 +5509,9 @@ names no camera/motion move **and** no exclusions — the six-decision doctrine)
|
|
|
5474
5509
|
(the hook, scene 0, is thin across two or more of the four hook layers — text/sound/visual/vibe). Warnings never
|
|
5475
5510
|
block a run.
|
|
5476
5511
|
|
|
5477
|
-
#### `baker canvas critique <file.json>`
|
|
5512
|
+
#### `baker canvas critique <slug|file.json>`
|
|
5478
5513
|
|
|
5479
|
-
Pre-spend creative critic — **advisory, never blocks**. Scores a scaffolded creative 0–1 before a billed `run`,
|
|
5514
|
+
Takes a creative's slug or a canvas path, like `validate`. Pre-spend creative critic — **advisory, never blocks**. Scores a scaffolded creative 0–1 before a billed `run`,
|
|
5480
5515
|
so a weak spot gets fixed for free. It branches on modality: a **video** creative is scored on **hook strength**,
|
|
5481
5516
|
**sound-off first-frame legibility**, **retention risk**, and **mechanism clarity**; a **static ad** on **baked-text
|
|
5482
5517
|
legibility risk**, **identity grounding** (Rule 1 — every subject on a real reference), **brand-type fidelity** (a
|
|
@@ -5493,9 +5528,9 @@ $ baker canvas critique my-static-ad.json
|
|
|
5493
5528
|
"dimensions": [ { "dimension": "text_legibility_risk", "score": 1, "note": "2 copy block(s) — reads cleanly" }, … ] }
|
|
5494
5529
|
```
|
|
5495
5530
|
|
|
5496
|
-
#### `baker canvas run <file.json> [flags]`
|
|
5531
|
+
#### `baker canvas run <slug|file.json> [flags]`
|
|
5497
5532
|
|
|
5498
|
-
Validate, then execute the graph. Blocks until done. Logs one line per node. Returns `{ ok, run_id, outputs_dir, output, stats }` on stdout.
|
|
5533
|
+
Takes a creative's slug (`baker canvas run spring-offer`) or a canvas path, resolved like `validate`. Validate, then execute the graph. Blocks until done. Logs one line per node. Returns `{ ok, run_id, outputs_dir, output, stats }` on stdout.
|
|
5499
5534
|
|
|
5500
5535
|
| Flag | Default | Effect |
|
|
5501
5536
|
|---|---|---|
|
|
@@ -5507,6 +5542,9 @@ Validate, then execute the graph. Blocks until done. Logs one line per node. Ret
|
|
|
5507
5542
|
| `--remote-cache <on\|off>` | `on` (or `BAKER_CANVAS_REMOTE_CACHE`) | Company-scoped remote cache + durable asset persistence. |
|
|
5508
5543
|
| `--max-credits <n>` | uncapped (or `BAKER_CANVAS_MAX_CREDITS`) | Credit ceiling: aborts before billing when the estimate exceeds it, and at layer boundaries once actual spend does. Completed nodes stay cached, so retrying with a higher cap loses nothing. |
|
|
5509
5544
|
| `--no-record` | records | Skip posting the durable run-history record (and its live progress). |
|
|
5545
|
+
| `--draft` | off | A static ad only: render one picture at `high` quality with no judge, then draw and review every format exactly as the final does. Iterate on the copy and the layout with it, and run once without it for the final (three pictures at the best quality, judged). The canvas file is not changed. |
|
|
5546
|
+
|
|
5547
|
+
`stats.total_credits` is what the credits ledger debits for the run: each node's charge as the backend billed it (an estimate only when it reported none), plus the review's own model calls. `validate`'s figure is an estimate.
|
|
5510
5548
|
|
|
5511
5549
|
**Run history streams live.** The run posts its plan (every node + its dependency edges) the moment validation passes, then re-posts a progress snapshot as each node starts and settles — the dashboard's creative workflow graph shows nodes flipping pending → running → done in real time, with each node's outputs attached as they land. A failed run keeps its per-node trail (what completed, what died). All best-effort: an unreachable backend never changes the run's outcome.
|
|
5512
5550
|
|
|
@@ -5613,7 +5651,9 @@ The two scaffold passes are billed (the full `video_deconstruct` is the heavy on
|
|
|
5613
5651
|
|
|
5614
5652
|
#### `baker canvas scaffold-static-ad <image> [flags]`
|
|
5615
5653
|
|
|
5616
|
-
`<image>` is a **local path or an `http(s)` URL** (e.g. a presigned S3 link from ad-dna). A URL is passed straight
|
|
5654
|
+
`<image>` is a **local path or an `http(s)` URL** (e.g. a presigned S3 link from ad-dna). A URL is passed straight in; do not `curl` it to a file first. The source is uploaded to durable storage before any billed pass, so the canvas keeps running after a presigned URL expires.
|
|
5655
|
+
|
|
5656
|
+
**Every scaffold is a creative in `src/creatives/<slug>/`** (`<slug>.canvas.json`, `prompt.json`, `_definition.md`), never a loose file in the workspace. Without `--slug` a slug is derived from `--title`, else the reference's file name, else `static-ad`, suffixed `-2`, `-3`… past any folder that already exists, and a hint names it. Every flag that shapes the bill (`--platform`, `--placements`, `--aspect`, `--takes`, `--avatar`) is checked before the first billed pass; an unknown value fails with `invalid_platform`, `invalid_placements`, `invalid_aspect`, `invalid_takes` or `avatar` and costs nothing. The output ends with `next_steps`: the one file to edit (`prompt.json`) and the exact command to run (`baker canvas run <slug>`).
|
|
5617
5657
|
|
|
5618
5658
|
Turn a source/inspiration image into a **runnable, self-validated static-ad canvas** — the static counterpart of `scaffold-video`. Like the video scaffold, this runs **billed Gemini passes** up front:
|
|
5619
5659
|
|
|
@@ -5621,35 +5661,46 @@ Turn a source/inspiration image into a **runnable, self-validated static-ad canv
|
|
|
5621
5661
|
2. **element selection** (`~google/gemini-flash-latest`) — picks the **main, identity-critical** elements (the brand logo, a showcased product, a trust badge) **plus any foreground/hero person or animal** — the emotional focal point — even a generic one, because a free-generated face/muzzle reads as AI and grows artifacts; the emotional hero always gets a real-reference slot. When the advertiser's logo appears in **more than one lockup** (a square/icon **mark** and a horizontal **wordmark**), each is emitted as its **own** element (e.g. `LOGO_MARK`, `LOGO_WORDMARK`) so you drop the right file in each slot instead of stretching one logo to cover both. The describe pass also records the ad's **typography** under a `fonts` block (each typeface's classification, a best-guess family, and its weight/case) so you know exactly what to drop at the brand-font slot. Background extras are dropped. Each element is stamped back onto its blueprint entry as a `reference_image` label so the JSON self-documents which slot grounds which subject.
|
|
5622
5662
|
3. **global layout** (`~google/gemini-flash-latest`) — produces a structured `layout` block in `prompt.json`: the column/row grid, each region's `x_pct`/`y_pct` bounds, panel splits, background/shape, and every text block's relative size/weight/case/alignment. This is what gives the generator a precise composition to rebuild.
|
|
5623
5663
|
|
|
5624
|
-
It then scaffolds a canvas that
|
|
5664
|
+
It then scaffolds a canvas that draws **only what `prompt.json` holds**; no element is mandatory. The report's `mode` names which of four the ad is:
|
|
5665
|
+
|
|
5666
|
+
- **`composed`**: the image model (`openai/gpt-image-2.5-sunburst`, quality `xhigh`) paints up to **three candidate plates** (`--takes`, default 3; the picture with no overlay text and no logo, keeping calm the areas the copy will occupy), an `image_select` judge scores them on one dominant idea, thumbnail legibility, mechanism kept, space for what is drawn, brand with the logo covered, artifacts, Stories safe zones and text, and every delivered format is drawn by an HTML composition (`layout-4x5/`, `layout-9x16/`, one per format, snapshotted by `hyperframe_snapshot`). Each line sits at its layout region, carried through the format's crop or extension into its safe area (a band only when the layout gives it no position), and is set the way the reference set it: plain, underlined, followed by an arrow, a button (in the brand CTA colour), a sticker or a highlight. A call to action is never forced into a pill.
|
|
5667
|
+
- **`picture_only`**: the concept has no copy, so the plate is the ad and nothing is drawn on it.
|
|
5668
|
+
- **`text_only`**: no picture is generated. The copy is drawn on the brand's lead colour in its fonts, for near-zero credits. On by default when the reference pictures nothing but copy (no subjects, no people); `--text-only` forces it, `--no-text-only` refuses it.
|
|
5669
|
+
- **`baked`**: the copy is painted into the plates, only with `--skip-font` or when no font can load; every other shape is re-framed by `image_aspect_adapt` on `google/gemini-3-pro-image`, never cropped.
|
|
5670
|
+
|
|
5671
|
+
**Fonts** come from the first source that has one: a file in `src/brand/fonts/` (via `src/brand/tokens.json` when present), else the heading and body families `tokens.json` or `BRAND.md`'s typography section name, fetched as web font files from Google Fonts or Fontsource and ingested by URL, else Inter (800 and 400). **The logo is optional**: it is drawn from `src/brand/logos/` only when `prompt.json` keeps a logo (a `logo` layout region, or an `advertiser_brand` entry in `brands_logos`) and a real file exists. Meta shows the page name and picture on every ad, so no logo is never an error, never a slot to fill and never a stand-in; a canvas scaffolded with an unfilled logo slot runs without it. Text that belongs to something in the scene (a phone note, a screenshot, a sign, packaging: `surface: "in_scene"` in `text_content`) is painted by the plate, not overlaid, and listed in the report's `in_scene_text`. The plates read `prompt.plate.json` (the scene, the reserved areas, the visual mechanisms and the in-scene text; no overlay copy, no brand names) and the compositions read `prompt.compose.json`; both are re-derived from `prompt.json` by `validate` and `run`, so a copy edit re-draws the formats from the cached plates without paying for a picture again. A format within 10% of the plate's shape is a free crop; a farther one extends the chosen plate once on the same model. Detected elements get **one `[TODO]` ingest slot** each (never the logo), and the original image is wired in for composition only, left out of the render when it shows a person, because the generator copies that face whatever the prompt says (the blueprint's layout carries the composition instead). Each **animal hero** is additionally fused into a generated **multi-view reference sheet** (`image_reference_sheet`, a turnaround built from the one dropped photo) that the render grounds on instead of the lone flat snapshot. Pass `--skip-actor-sheets` to ground straight on the dropped photo. A **person hero** is cast from a company Avatar with `--avatar <handle>`: the slot ingests its identity sheet and `prompt.json` describes that person in the Avatar's own words. The render keeps the face it is given, so it is never a found photo of a real person. The canvas is validated before it's written. stdout returns `{ ok, slug, canvas_path, prompt_path, models, aspect_ratio, placements, output_formats, layout_regions, stats, checklist, next_steps }`; the **checklist** carries `mode`, `summary` (what a run does: pictures, judge, fonts, logo), `fonts`, `logo`, `in_scene_text` and every real asset to drop in.
|
|
5672
|
+
|
|
5673
|
+
**Placement formats.** The canvas delivers the platform's full placement set, each format at its exact size, collected under `final` with the format as its label. The set is derived from `--platform` (`meta` → 4:5 + 9:16, `google` → 1:1 + 4:5 + 16:9, `linkedin` → 1:1, `tiktok` → 9:16, `youtube` → 16:9); override it with `--placements <preset>` or `--placements none` for a single format. The report's `aspect_ratio` is the ratio the plates really render at (the model's nearest to the lead format), and `format_plan` says how each format is made.
|
|
5625
5674
|
|
|
5626
|
-
**
|
|
5675
|
+
**After a run** the CLI reads the finished ad back on what makes a static win, not a checklist of elements: every delivered image's pixel shape against its format, and one vision pass that reads each word against the copy `prompt.json` holds and checks one dominant idea, clipping, artifacts, the brand with the logo covered and the concept's mechanisms, with every format shown a second time at phone-feed thumbnail size (240px wide, same call): a lead message unreadable at that size is blocking. A missing logo, CTA or copy is never a finding; a logo that is drawn and broken is. stdout carries `verdict` (`ready` | `ready_with_notes` | `needs_fixes` | `not_reviewed`), `judge` (the per-plate scores) and hints that end with what the run made; the findings are stored on the run as its `review`. `needs_fixes` means the ad is not done even though every node completed.
|
|
5627
5676
|
|
|
5628
|
-
The
|
|
5677
|
+
The logo is never a slot to fill: use the company's committed logo in `src/brand/logos/` (per `BRAND.md`), never `baker images logo <domain>`, whose Brandfetch CDN often serves an outdated mark.
|
|
5629
5678
|
|
|
5630
|
-
The
|
|
5679
|
+
The plate prompt is **model-aware**: for **gpt-image** the exclusions (no invented badges/watermark) are hoisted to the very *end* of the prompt where the model reads them as pure exclusions rather than composition. In the baked-copy fallback it quotes every line of `prompt.json`'s copy (never the logo's text) with an instruction to render each **once, verbatim**; that block is re-read from `prompt.json` on every `validate` and `run`, which also upgrades canvases scaffolded before this change. Run `baker canvas critique` on the scaffolded canvas for an advisory read before you spend.
|
|
5631
5680
|
|
|
5632
5681
|
```bash
|
|
5633
|
-
baker canvas scaffold-static-ad ./reference-ad.png --context "competitor ad for <brand>, <category>, <market>"
|
|
5634
|
-
# → writes
|
|
5682
|
+
baker canvas scaffold-static-ad ./reference-ad.png --slug spring-offer --context "competitor ad for <brand>, <category>, <market>"
|
|
5683
|
+
# → writes src/creatives/spring-offer/{spring-offer.canvas.json,prompt.json,_definition.md}
|
|
5635
5684
|
# edit prompt.json into your ad + replace each [TODO] ingest path with a real file, then:
|
|
5636
|
-
baker canvas validate
|
|
5637
|
-
baker canvas run
|
|
5685
|
+
baker canvas validate spring-offer
|
|
5686
|
+
baker canvas run spring-offer
|
|
5638
5687
|
```
|
|
5639
5688
|
|
|
5640
5689
|
| Flag | Default | Effect |
|
|
5641
5690
|
|---|---|---|
|
|
5642
5691
|
| `--context <text>` | — | Known provenance (advertiser, category, market) to ground the describe. |
|
|
5643
|
-
| `--
|
|
5644
|
-
| `--
|
|
5692
|
+
| `--slug <slug>` | derived from `--title` or the reference | Creative slug (lowercase kebab): the creative's folder `src/creatives/<slug>/`, which attaches every run to its generation history. |
|
|
5693
|
+
| `--title <text>` | title-cased slug | Creative title for `_definition.md`; also the source of a derived slug. |
|
|
5645
5694
|
| `--describe-model <id>` | registry default (`~google/gemini-pro-latest`) | Override the `image_describe` model. |
|
|
5646
5695
|
| `--select-model <id>` | registry default (`~google/gemini-flash-latest`) | Override the element-selection `text_generate` model. |
|
|
5647
5696
|
| `--layout-model <id>` | registry default (`~google/gemini-flash-latest`) | Override the global-layout `text_generate` model. |
|
|
5648
|
-
| `--gen-model <id>` |
|
|
5649
|
-
| `--
|
|
5697
|
+
| `--gen-model <id>` | `openai/gpt-image-2.5-sunburst` | Override the plate model. |
|
|
5698
|
+
| `--takes <n>` | `3` | Plates rendered and judged (1-3); `1` skips the judge. A text-only ad renders none. |
|
|
5699
|
+
| `--text-only` | on when the reference pictures nothing but copy (a note, a letter, a comparison with no photo) | Draw a typographic ad: no picture generated; each line in the colour, weight and size prompt.json gives it, on the layout's background colour, with its cards, dividers, icons, tick and cross marks and app chrome. `--no-text-only` renders a picture anyway. |
|
|
5700
|
+
| `--platform <channel>` | `meta` | `_definition.md` channel **and** the default placement set the hero fans out to (`meta`\|`google`\|`linkedin`\|`tiktok`\|`youtube`\|`x`\|`other`; `x`/`other` fan out to nothing). Any other value is refused. |
|
|
5650
5701
|
| `--placements <preset\|none>` | derived from `--platform` | Override the fan-out set: `meta_feed`\|`meta_feed_stories`\|`meta_all`\|`linkedin`\|`google_demandgen`\|`tiktok`\|`youtube`, or `none` for a single base ad. |
|
|
5651
|
-
| `--aspect <ratio>` | inferred from the image, else `9:16` | Force the
|
|
5652
|
-
| `--skip-font` | off |
|
|
5702
|
+
| `--aspect <ratio>` | inferred from the image, else `9:16` | Force the lead format (it leads the placement set when the set has it): `1:1`, `4:5`, `9:16`, `16:9`, `4:3`, `3:4`, `2:3`, `3:2` or `21:9`. |
|
|
5703
|
+
| `--skip-font` | off | Bake the copy into the plates instead of drawing it in a real font (a font file, else the typeface the brand names, else Inter). |
|
|
5653
5704
|
| `--skip-actor-sheets` | off | Ground each animal hero on its lone dropped photo instead of a generated multi-view reference sheet. |
|
|
5654
5705
|
| `--avatar <handles>` | — | Cast company avatars as the image's people, comma-separated in the order they were detected (`marta` or `marta,joan`). Each person slot grounds on that avatar's identity sheet and takes its subject description. Checked before any billed pass; an unknown, not-ready or sheetless avatar fails with code `avatar`. A handle with no person to play is reported in `hints` as `NOT CAST`. |
|
|
5655
5706
|
|
|
@@ -5675,7 +5726,7 @@ baker canvas rerun spring-offer --force-remote --regenerate gen
|
|
|
5675
5726
|
|
|
5676
5727
|
Use it when a creative was built in another conversation (or its sandbox is gone) and you need to continue or re-render it here. Files the snapshot could not include (missing at run time, or oversized) are listed as warnings — supply those locally only if the run actually needs to regenerate the nodes that read them.
|
|
5677
5728
|
|
|
5678
|
-
#### `baker canvas inspect <run_id> [--thumbnails]`
|
|
5729
|
+
#### `baker canvas inspect <run_id|slug> [--thumbnails]`
|
|
5679
5730
|
|
|
5680
5731
|
One-page summary of a completed run: per-node duration + cache status, list of files in the run dir, optional video thumbnails (start/middle/end frames extracted via ffmpeg).
|
|
5681
5732
|
|
|
@@ -5684,7 +5735,7 @@ baker canvas inspect r_01JXYZ...
|
|
|
5684
5735
|
baker canvas inspect r_01JXYZ... --thumbnails
|
|
5685
5736
|
```
|
|
5686
5737
|
|
|
5687
|
-
Resolves the run dir against `--outputs-dir` (default `./canvas`) or accepts an absolute path. Reads `manifest.json` for per-node stats.
|
|
5738
|
+
Resolves the run dir against `--outputs-dir` (default `./canvas`) or accepts an absolute path. A creative's slug (`baker canvas inspect spring-offer`) opens that creative's latest run. Reads `manifest.json` for per-node stats.
|
|
5688
5739
|
|
|
5689
5740
|
#### `baker canvas catalog`
|
|
5690
5741
|
|