@koda-sl/baker-cli 0.387.0 → 0.389.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.
Files changed (28) hide show
  1. package/README.md +147 -33
  2. package/canvas/static-ad-composition/index.html +253 -0
  3. package/canvas/static-ad-composition/meta.json +53 -0
  4. package/dist/{chunk-HJEMOSPH.js → chunk-7OVZ6SOA.js} +13 -2
  5. package/dist/{chunk-HJEMOSPH.js.map → chunk-7OVZ6SOA.js.map} +1 -1
  6. package/dist/{chunk-WLDM2TRF.js → chunk-EYN7G4KF.js} +4 -4
  7. package/dist/{chunk-FGHMWBUD.js → chunk-IOWWKZRF.js} +128 -19
  8. package/dist/chunk-IOWWKZRF.js.map +1 -0
  9. package/dist/{chunk-XHC4LMK4.js → chunk-J76AGFBH.js} +7 -7
  10. package/dist/{chunk-VZEDAFAR.js → chunk-TWSWUIDI.js} +2 -2
  11. package/dist/{chunk-MF7YA62W.js → chunk-Z7WHG6TA.js} +3 -3
  12. package/dist/cli.js +43947 -15763
  13. package/dist/cli.js.map +1 -1
  14. package/dist/{client-GR6YL3YX.js → client-YUWLIYLF.js} +4 -4
  15. package/dist/engine/index.js +3 -3
  16. package/dist/{env-4P5HOGNA.js → env-RQGMYIQN.js} +2 -2
  17. package/dist/{output-NMQXZF3Z.js → output-POG3LVBW.js} +5 -5
  18. package/dist/{shared-6762QE6R.js → shared-ZAEUTUNU.js} +6 -6
  19. package/package.json +4 -1
  20. package/dist/chunk-FGHMWBUD.js.map +0 -1
  21. /package/dist/{chunk-WLDM2TRF.js.map → chunk-EYN7G4KF.js.map} +0 -0
  22. /package/dist/{chunk-XHC4LMK4.js.map → chunk-J76AGFBH.js.map} +0 -0
  23. /package/dist/{chunk-VZEDAFAR.js.map → chunk-TWSWUIDI.js.map} +0 -0
  24. /package/dist/{chunk-MF7YA62W.js.map → chunk-Z7WHG6TA.js.map} +0 -0
  25. /package/dist/{client-GR6YL3YX.js.map → client-YUWLIYLF.js.map} +0 -0
  26. /package/dist/{env-4P5HOGNA.js.map → env-RQGMYIQN.js.map} +0 -0
  27. /package/dist/{output-NMQXZF3Z.js.map → output-POG3LVBW.js.map} +0 -0
  28. /package/dist/{shared-6762QE6R.js.map → shared-ZAEUTUNU.js.map} +0 -0
package/README.md CHANGED
@@ -157,7 +157,7 @@ Use `--output` to change format:
157
157
 
158
158
  ### Ad Platforms (`baker ads`)
159
159
 
160
- Multi-platform ad commands. Google, Meta, and LinkedIn support both reads and **staged writes** (create/update campaigns, ad sets/ad groups, ads, creatives, audiences — applied on chat publish); X (Twitter) is read + analytics. TikTok is coming.
160
+ Multi-platform ad commands. Google, Meta, and LinkedIn support both reads and **staged writes** (create/update campaigns, ad sets/ad groups, ads, creatives, audiences — applied on chat publish); X (Twitter) is read + analytics; ChatGPT Ads is read + analytics + staged writes. TikTok is coming.
161
161
 
162
162
  ---
163
163
 
@@ -863,6 +863,48 @@ baker ads x targeting-constants --constant tv_shows --locale en-US
863
863
 
864
864
  ---
865
865
 
866
+ ### ChatGPT Ads (`baker ads chatgpt`)
867
+
868
+ Read the company's ChatGPT Ads account (OpenAI's ads inside ChatGPT conversations) and how it performs, and stage changes to it that apply when the chat is published. The company connects one account in the dashboard (Brain → Integrations → ChatGPT Ads) with an API key from its Ads Manager; the key stays in Baker and never reaches the CLI, so there is no account id to pass.
869
+
870
+ | Subcommand | What it returns |
871
+ |---|---|
872
+ | `account` | Name, currency, timezone, status, and the brand and account reviews that decide whether anything serves |
873
+ | `campaigns` | Campaigns: objective, daily or lifetime budget (currency units), schedule, locations, platforms, serving issues |
874
+ | `ad-groups` | Ad groups (`--campaign-id`): bid strategy, max bid, context hints, serving issues |
875
+ | `ads` | Ads (`--ad-group-id`): the card's copy, image, destination, review outcome, serving issues |
876
+ | `conversions` | Pixels (with the pixel id a landing page installs) and conversions, with the campaigns using each |
877
+ | `insights` | Impressions, clicks, spend, CTR, CPC, CPM, conversions, CPA, ROAS — per `--level account\|campaign\|ad_group\|ad`, over `--days N` or `--since/--until`, `--granularity`, `--segment country\|device\|platform\|product` |
878
+ | `campaigns create\|update\|pause\|resume\|archive` | Stage a campaign change: objective, daily or lifetime budget (currency units), schedule, countries and regions included and excluded, audiences, platforms, tracking template, status, the conversions it measures (`--conversion-event-ids`), a catalog (`--catalog-id`) |
879
+ | `ad-groups create\|update\|pause\|resume\|archive` | Stage an ad group change: context hints, bid strategy, max bid, audience bid adjustments, tracking template, product filters, status |
880
+ | `ads create\|update\|pause\|resume\|archive` | Stage a chat card: title, body, landing page, image and its crop, tracking template, status |
881
+ | `ads create-product` | Stage a catalog ad group's product ad: a title template the catalog's products fill in |
882
+ | `pixels create` | Stage a new website pixel |
883
+ | `audiences` | Custom audiences, with `create\|add\|remove\|archive` — members hashed on your machine, never sent raw |
884
+ | `catalogs` | Product catalogs, with `uploads` (how each upload went) and `products` (preview filters) |
885
+ | `conversions report` | Conversions split into click-through and view-through, with the attribution windows |
886
+ | `history` | What changed in the account, by anyone, with each field before and after |
887
+ | `account update` | Stage the advertiser's brand name or icon (`--favicon-url`) |
888
+ | `conversions create` | Stage a conversion — a standard event (`lead_created`, `order_created`, …) or a custom one by name — counted from the account's pixel |
889
+ | `conversions events` | What the pixel delivered in the last ~15 minutes — proof a site sends the event |
890
+ | `locations <place>` | Regions, cities and markets ChatGPT Ads can target, with the id `--location-ids` takes |
891
+ | `draft` | Review the staged changes as a tree; `show`, `amend`, `remove`, `clear`. After a publish, what applied and what failed |
892
+
893
+ `--full` on the list commands returns ChatGPT Ads' own objects. ChatGPT Ads refuses some metric and breakdown combinations (CPA, conversion rate and sales cannot be broken down; conversions come daily or as one total); `insights` refuses them first and says why.
894
+
895
+ Writes need `BAKER_CHAT_ID`: they stage on the chat and reach ChatGPT Ads only when it is published. New entities are created paused unless `--status active`.
896
+
897
+ ```bash
898
+ baker ads chatgpt account
899
+ baker ads chatgpt insights --days 30 --granularity daily --level account
900
+ baker ads chatgpt insights --campaign-id cmpn_123 --level ad --sort spend
901
+ baker ads chatgpt campaigns create --name "Spring push" --objective clicks --daily-budget 20 --countries ES
902
+ baker ads chatgpt ad-groups create --campaign-id chatgpt_temp_ab12 --name "Rooftop owners" --context-hints "solar panels for my house" --max-bid 2.50
903
+ baker ads chatgpt draft
904
+ ```
905
+
906
+ ---
907
+
866
908
  ### Meta Ads (`baker ads meta`)
867
909
 
868
910
  Meta Marketing API (Facebook + Instagram). Connect via OAuth in the dashboard, pick which ad accounts to scope to, then call from the CLI. Account ID via `--account-id act_123` or `BAKER_META_AD_ACCOUNT_ID` env var.
@@ -3788,7 +3830,7 @@ Starting a batch returns 202 + a `generationId`; the CLI polls until the takes s
3788
3830
  ```bash
3789
3831
  baker studio generate "a friendly golden retriever in a bright modern living room" --aspect-ratio 16:9
3790
3832
  baker studio generate "hero shot of a matte black water bottle on wet marble, studio light" \
3791
- --model google/gemini-3-pro-image-preview --image-size 2K --aspect-ratio 4:5 --count 3
3833
+ --model google/gemini-3-pro-image --image-size 2K --aspect-ratio 4:5 --count 3
3792
3834
  baker studio generate "home office hero, warm minimalist, natural light, 35mm photo" \
3793
3835
  --reference "https://i.pinimg.com/…/pin1.jpg,./src/brand/logos/product.png,j57abc123def456ghi789"
3794
3836
  baker studio generate "founder-style selfie, kitchen background" --skill ugc-selfie-hook
@@ -3796,13 +3838,13 @@ baker studio generate "flat geometric mascot, brand palette" \
3796
3838
  --model recraft/recraft-v4.1-pro-vector --rgb-colors "[[10,10,10],[255,80,0]]" --bg-rgb "[255,255,255]"
3797
3839
  ```
3798
3840
 
3799
- **Models** (`--model`, default `google/gemini-3.1-flash-image-preview`):
3841
+ **Models** (`--model`, default `google/gemini-3.1-flash-image`):
3800
3842
 
3801
3843
  | Model | Best for | Aspect ratios | Sizes |
3802
3844
  |---|---|---|---|
3803
- | `google/gemini-3.1-flash-image-preview` **(default)** | Fast iteration, reference editing, extreme aspect ratios (Nano Banana flash) | standard **+** `1:4` `4:1` `1:8` `8:1` | `0.5K`–`4K` |
3845
+ | `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` |
3804
3846
  | `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** |
3805
- | `google/gemini-3-pro-image-preview` | Highest fidelity (Nano Banana Pro) — strong prompt adherence and reference grounding | standard set | `1K` `2K` `4K` |
3847
+ | `google/gemini-3-pro-image` | Highest fidelity (Nano Banana Pro) — strong prompt adherence and reference grounding | standard set | `1K` `2K` `4K` |
3806
3848
  | `openai/gpt-image-2` | Photoreal + cleanest in-image text — ad/landing reproduction | standard set **minus** `4:5` `5:4` | *(none — derived from the ratio)* |
3807
3849
  | `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) |
3808
3850
  | `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`)* |
@@ -3811,7 +3853,7 @@ Standard aspect ratios: `1:1` `2:3` `3:2` `3:4` `4:3` `4:5` `5:4` `9:16` `16:9`
3811
3853
 
3812
3854
  `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.
3813
3855
 
3814
- `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-preview` 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.
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` 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.
3815
3857
 
3816
3858
  `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.
3817
3859
 
@@ -3821,7 +3863,7 @@ A knob the chosen model does not advertise is **refused, not silently dropped**
3821
3863
 
3822
3864
  | Flag | Description |
3823
3865
  |---|---|
3824
- | `--model` | Model id (default `google/gemini-3.1-flash-image-preview`) |
3866
+ | `--model` | Model id (default `google/gemini-3.1-flash-image`) |
3825
3867
  | `--aspect-ratio` | Output aspect ratio (default `1:1`) |
3826
3868
  | `--count` | Takes to render, 1–8 (default 1) |
3827
3869
  | `--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. |
@@ -3929,7 +3971,7 @@ Compact output per take is `{ slot, status, imageId | videoId, url, inLibrary, e
3929
3971
  | `lifestyle-scene` | `openai/gpt-image-2` | `4:5` | 3 | **yes** |
3930
3972
  | `cinematic-still` | `google/gemini-3-pro-image-preview` | `16:9` | 3 | no |
3931
3973
  | `brand-icon` | `recraft/recraft-v4.1-pro-vector` | `1:1` | 4 | no |
3932
- | `background-plate` | `google/gemini-3.1-flash-image-preview` | `4:5` | 3 | no |
3974
+ | `background-plate` | `google/gemini-3.1-flash-image` | `4:5` | 3 | no |
3933
3975
 
3934
3976
  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.
3935
3977
 
@@ -4121,7 +4163,7 @@ A re-run with no changes hits the cache for every node — total runtime drops t
4121
4163
  "type": "text_generate",
4122
4164
  "inputs": { "topic": "$ref:topic.text" },
4123
4165
  "params": {
4124
- "model": "google/gemini-3.1-flash-image-preview",
4166
+ "model": "google/gemini-3.1-flash-image",
4125
4167
  "prompt": "Write a 6-word tagline for {{topic}}."
4126
4168
  }
4127
4169
  }
@@ -4303,7 +4345,7 @@ Example pipeline:
4303
4345
  { "id": "font", "type": "ingest", "params": { "source": "url", "url": "https://cdn.example.com/brand.woff2", "expect": "font" } }
4304
4346
  { "id": "specimen", "type": "font_specimen", "inputs": { "font": "$ref:font.asset" }, "params": {} }
4305
4347
  { "id": "gen", "type": "image_generate", "inputs": { "reference": "$ref:specimen.image" },
4306
- "params": { "model": "google/gemini-3.1-flash-image-preview", "prompt": "Launch poster headlined 'SALE ENDS FRIDAY' using the exact typeface from the reference image" } }
4348
+ "params": { "model": "google/gemini-3.1-flash-image", "prompt": "Launch poster headlined 'SALE ENDS FRIDAY' using the exact typeface from the reference image" } }
4307
4349
  ```
4308
4350
 
4309
4351
  ---
@@ -4442,13 +4484,13 @@ No `image_size`: OpenAI derives the pixel dimensions from the ratio, so a size i
4442
4484
 
4443
4485
  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.
4444
4486
 
4445
- ###### Model: `google/gemini-3.1-flash-image-preview`
4487
+ ###### Model: `google/gemini-3.1-flash-image` (legacy id `google/gemini-3.1-flash-image-preview` still accepted)
4446
4488
 
4447
4489
  Gemini flash image model — the default. Fast iteration, reference editing, extreme aspect ratios, and the `0.5K` size for cheap previews.
4448
4490
 
4449
4491
  | Name | Type | Required | Notes |
4450
4492
  |---|---|---|---|
4451
- | `model` | literal | yes | `"google/gemini-3.1-flash-image-preview"` |
4493
+ | `model` | literal | yes | `"google/gemini-3.1-flash-image"` |
4452
4494
  | `prompt` | string | yes | non-empty |
4453
4495
  | `aspect_ratio` | enum | no | EXTREME AR |
4454
4496
  | `image_size` | enum | no | `0.5K \| 1K \| 2K \| 4K` |
@@ -4464,20 +4506,20 @@ Gemini flash image model — the default. Fast iteration, reference editing, ext
4464
4506
  | `aspect_ratio` | enum | no | EXTREME AR — keeps `4:5` / `5:4`, so Meta/Instagram portrait formats come straight out of it |
4465
4507
  | `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 |
4466
4508
 
4467
- 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-preview` or `google/gemini-3-pro-image-preview`. Registered for `image_generate` only; `image_reference_sheet` and `image_aspect_adapt` do not accept it.
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` or `google/gemini-3-pro-image`. Registered for `image_generate` only; `image_reference_sheet` and `image_aspect_adapt` do not accept it.
4468
4510
 
4469
4511
  ```json
4470
4512
  { "id": "variant_a", "type": "image_generate",
4471
4513
  "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" } }
4472
4514
  ```
4473
4515
 
4474
- ###### Model: `google/gemini-3-pro-image-preview`
4516
+ ###### Model: `google/gemini-3-pro-image` (legacy id `google/gemini-3-pro-image-preview` still accepted)
4475
4517
 
4476
4518
  "Nano Banana Pro" — heavier, higher-quality Gemini.
4477
4519
 
4478
4520
  | Name | Type | Required | Notes |
4479
4521
  |---|---|---|---|
4480
- | `model` | literal | yes | `"google/gemini-3-pro-image-preview"` |
4522
+ | `model` | literal | yes | `"google/gemini-3-pro-image"` |
4481
4523
  | `prompt` | string | yes | non-empty |
4482
4524
  | `aspect_ratio` | enum | no | STD AR |
4483
4525
  | `image_size` | enum | no | `1K \| 2K \| 4K` |
@@ -4521,7 +4563,7 @@ The sheet prompt is baked into the backend — there is no `prompt` param. `subj
4521
4563
 
4522
4564
  | Name | Type | Required | Notes |
4523
4565
  |---|---|---|---|
4524
- | `model` | enum | yes | one of `google/gemini-3-pro-image-preview`, `google/gemini-3.1-flash-image-preview` |
4566
+ | `model` | enum | yes | one of `google/gemini-3-pro-image`, `google/gemini-3.1-flash-image` (their `-preview` ids still accepted) |
4525
4567
  | `subject_description` | string | yes | exact subject wording — reuse verbatim downstream |
4526
4568
  | `subject_type` | enum | yes | `character \| person \| product` — picks the template + default views |
4527
4569
  | `views` | string[] | no | 2–6 view labels; defaults per `subject_type` (below) |
@@ -4548,7 +4590,7 @@ Default views per `subject_type`:
4548
4590
 
4549
4591
  Guidance:
4550
4592
  - 3–4 full-body views at `16:9` is the sweet spot for characters; more views shrink each figure and lose face detail.
4551
- - Use `google/gemini-3.1-flash-image-preview` while iterating; switch to `google/gemini-3-pro-image-preview` at `2K`+ for final 6-view sheets.
4593
+ - Use `google/gemini-3.1-flash-image` while iterating; switch to `google/gemini-3-pro-image` at `2K`+ for final 6-view sheets.
4552
4594
  - Feed the whole sheet downstream as one `reference` input — don't crop individual views.
4553
4595
 
4554
4596
  **Example** — two photos of a sneaker → product sheet → consistent hero shot:
@@ -4561,14 +4603,14 @@ Guidance:
4561
4603
  { "id": "b", "type": "ingest", "params": { "source": "url", "url": "https://example.com/sneaker-side.jpg", "expect": "image" } },
4562
4604
  { "id": "sheet", "type": "image_reference_sheet",
4563
4605
  "inputs": { "references": ["$ref:a.asset", "$ref:b.asset"] },
4564
- "params": { "model": "google/gemini-3-pro-image-preview",
4606
+ "params": { "model": "google/gemini-3-pro-image",
4565
4607
  "subject_description": "navy leather low-top sneaker with white sole and gold logo",
4566
4608
  "subject_type": "product",
4567
4609
  "style": "matte black studio background, soft even key light",
4568
4610
  "aspect_ratio": "16:9", "image_size": "2K" } },
4569
4611
  { "id": "hero", "type": "image_generate",
4570
4612
  "inputs": { "reference": "$ref:sheet.sheet" },
4571
- "params": { "model": "google/gemini-3-pro-image-preview",
4613
+ "params": { "model": "google/gemini-3-pro-image",
4572
4614
  "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" } }
4573
4615
  ],
4574
4616
  "output": { "node": "hero", "output": "images" }
@@ -4593,7 +4635,7 @@ The adaptation prompt is baked into the backend and is direction-aware (a wider
4593
4635
 
4594
4636
  | Name | Type | Required | Notes |
4595
4637
  |---|---|---|---|
4596
- | `model` | enum | yes | one of `google/gemini-3-pro-image-preview`, `google/gemini-3.1-flash-image-preview` |
4638
+ | `model` | enum | yes | one of `google/gemini-3-pro-image`, `google/gemini-3.1-flash-image` (their `-preview` ids still accepted) |
4597
4639
  | `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 |
4598
4640
  | `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`. |
4599
4641
  | `guidance` | string | no | hints appended to the baked prompt, e.g. `"keep the CTA button fully visible"` |
@@ -4610,7 +4652,7 @@ The adaptation prompt is baked into the backend and is direction-aware (a wider
4610
4652
  Guidance:
4611
4653
  - 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.
4612
4654
  - List the source's own ratio in `formats` too when you want the full set in one output — it passes through for free.
4613
- - Use `google/gemini-3.1-flash-image-preview` while iterating; switch to `google/gemini-3-pro-image-preview` for final-quality adaptation.
4655
+ - Use `google/gemini-3.1-flash-image` while iterating; switch to `google/gemini-3-pro-image` for final-quality adaptation.
4614
4656
  - Indexes are positional: `$ref:adapt.images#1` is always the second entry of `formats`.
4615
4657
 
4616
4658
  **Example** — one 9:16 story creative fanned out to feed and landscape:
@@ -4620,12 +4662,12 @@ Guidance:
4620
4662
  "schema": "baker-canvas/1",
4621
4663
  "nodes": [
4622
4664
  { "id": "hero", "type": "image_generate",
4623
- "params": { "model": "google/gemini-3-pro-image-preview",
4665
+ "params": { "model": "google/gemini-3-pro-image",
4624
4666
  "prompt": "Story-format ad for a citrus energy drink: can centered, bold OFFER text top, logo bottom",
4625
4667
  "aspect_ratio": "9:16", "image_size": "2K" } },
4626
4668
  { "id": "adapt", "type": "image_aspect_adapt",
4627
4669
  "inputs": { "source": "$ref:hero.images#0" },
4628
- "params": { "model": "google/gemini-3-pro-image-preview",
4670
+ "params": { "model": "google/gemini-3-pro-image",
4629
4671
  "formats": ["9:16", "1:1", "16:9"],
4630
4672
  "guidance": "keep the offer text and logo fully visible" } }
4631
4673
  ],
@@ -4760,9 +4802,9 @@ Fail-fast: an invalid model selection (wrong count, duplicate or out-of-range pi
4760
4802
  {
4761
4803
  "schema": "baker-canvas/1",
4762
4804
  "nodes": [
4763
- { "id": "a", "type": "image_generate", "params": { "model": "google/gemini-3.1-flash-image-preview", "prompt": "hero shot of a ceramic mug, warm morning light" } },
4764
- { "id": "b", "type": "image_generate", "params": { "model": "google/gemini-3.1-flash-image-preview", "prompt": "hero shot of a ceramic mug, studio softbox" } },
4765
- { "id": "c", "type": "image_generate", "params": { "model": "google/gemini-3.1-flash-image-preview", "prompt": "hero shot of a ceramic mug, dramatic side light" } },
4805
+ { "id": "a", "type": "image_generate", "params": { "model": "google/gemini-3.1-flash-image", "prompt": "hero shot of a ceramic mug, warm morning light" } },
4806
+ { "id": "b", "type": "image_generate", "params": { "model": "google/gemini-3.1-flash-image", "prompt": "hero shot of a ceramic mug, studio softbox" } },
4807
+ { "id": "c", "type": "image_generate", "params": { "model": "google/gemini-3.1-flash-image", "prompt": "hero shot of a ceramic mug, dramatic side light" } },
4766
4808
  { "id": "best", "type": "image_select",
4767
4809
  "inputs": { "images": ["$ref:a.images#0", "$ref:b.images#0", "$ref:c.images#0"] },
4768
4810
  "params": { "model": "~google/gemini-pro-latest", "prompt": "most premium-feeling lighting, no harsh shadows" } }
@@ -5579,13 +5621,22 @@ Turn a source/inspiration image into a **runnable, self-validated static-ad canv
5579
5621
  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.
5580
5622
  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.
5581
5623
 
5582
- It then scaffolds a canvas that ingests `prompt.json`, wires **one `[TODO]` ingest slot per detected element** (plus an optional brand-font → type-specimen) into `image_generate`, and wires the original image 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 — the same identity lock the video scaffold uses, so the muzzle stays consistent and artifact-free from a single reference. 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, canvas_path, prompt_path, models, aspect_ratio, placements, output_formats, layout_regions, stats, checklist }` — the **checklist** lists every real asset to drop in (and which heroes get a sheet).
5624
+ 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:
5625
+
5626
+ - **`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.
5627
+ - **`picture_only`**: the concept has no copy, so the plate is the ad and nothing is drawn on it.
5628
+ - **`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.
5629
+ - **`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.
5583
5630
 
5584
- **Automatic placement fan-out.** By default the canvas doesn't stop at one ratio: the hero renders once at the platform's primary ratio, then an `image_aspect_adapt` node recomposes it into that platform's full placement set, and the canvas `output` points at the adapted images. The placement 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 disable it with `--placements none` for a single base ad. The hero ratio passes through the adapt node **free**; each other ratio is a billed recomposition — and because adaptation re-generates, every ratio should be eyeballed for text and brand-color fidelity before publish (`ad-formats.md` in the scaffold skill covers this).
5631
+ **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, canvas_path, prompt_path, models, aspect_ratio, placements, output_formats, layout_regions, stats, checklist }`; 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.
5585
5632
 
5586
- The checklist `note` reminds you to fill **logo** slots from the company's committed brand logo in `src/brand/logos/` (per `BRAND.md`) — **not** from `baker images logo <domain>`, whose Brandfetch CDN often serves an outdated mark (a common cause of an old logo shipping on a live ad).
5633
+ **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.
5587
5634
 
5588
- The generation prompt is **model-aware and typography-hardened** (a static ad is the only path that bakes text *into* the image — video frames are clean plates). It resolves the image model's profile, so 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, and it quotes every blueprint string with an instruction to render each **once, verbatim**, lock brand names letter-by-letter, and keep type crisp and legible — the fix for the #1 static-ad defect (garbled/duplicated/dropped copy). Run `baker canvas critique` on the scaffolded canvas for an advisory read on text-legibility risk, identity grounding, and brand-type fidelity before you spend.
5635
+ **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.
5636
+
5637
+ 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.
5638
+
5639
+ 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.
5589
5640
 
5590
5641
  ```bash
5591
5642
  baker canvas scaffold-static-ad ./reference-ad.png --context "competitor ad for <brand>, <category>, <market>"
@@ -5603,11 +5654,13 @@ baker canvas run ./static-ad.canvas.json
5603
5654
  | `--describe-model <id>` | registry default (`~google/gemini-pro-latest`) | Override the `image_describe` model. |
5604
5655
  | `--select-model <id>` | registry default (`~google/gemini-flash-latest`) | Override the element-selection `text_generate` model. |
5605
5656
  | `--layout-model <id>` | registry default (`~google/gemini-flash-latest`) | Override the global-layout `text_generate` model. |
5606
- | `--gen-model <id>` | registry default (`openai/gpt-image-2`) | Override the `image_generate` model. |
5657
+ | `--gen-model <id>` | `openai/gpt-image-2.5-sunburst` | Override the plate model. |
5658
+ | `--takes <n>` | `3` | Plates rendered and judged (1-3); `1` skips the judge. A text-only ad renders none. |
5659
+ | `--text-only` | on when the reference pictures nothing but copy | Draw a typographic ad: no picture generated, the copy on the brand colours in its fonts. `--no-text-only` renders a picture anyway. |
5607
5660
  | `--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). |
5608
5661
  | `--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. |
5609
- | `--aspect <ratio>` | inferred from the image, else `9:16` | Force the hero's aspect ratio (clamped into the placement set so it stays a free pass-through). |
5610
- | `--skip-font` | off | Skip the brand-font → type-specimen slot. |
5662
+ | `--aspect <ratio>` | inferred from the image, else `9:16` | Force the lead format (it leads the placement set when the set has it). |
5663
+ | `--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). |
5611
5664
  | `--skip-actor-sheets` | off | Ground each animal hero on its lone dropped photo instead of a generated multi-view reference sheet. |
5612
5665
  | `--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`. |
5613
5666
 
@@ -6123,6 +6176,67 @@ baker creatives publish ./canvas/<run_id>/<final>.png --title "Spring Offer 4x5"
6123
6176
 
6124
6177
  Without `--slug` the command behaves as before (one creative record per published image). With `--slug` it upserts the repo-convention row — the same one the dashboard's Creatives tab and the `src/creatives/{slug}/` folder describe — so publish, repo sync, and run history all land on a single record regardless of order.
6125
6178
 
6179
+ ### `baker creatives performance`
6180
+
6181
+ How each published creative is doing in the live Meta ads that show it. A creative is linked to an ad when a chat's publish uploads its image to Meta and creates an ad with it (or swaps an existing ad onto it); the results are read live from Meta.
6182
+
6183
+ ```bash
6184
+ baker creatives performance # every creative in an ad, last 30 days
6185
+ baker creatives performance --slug spring-offer # one creative
6186
+ baker creatives performance --days 14 --full # + ids, last-7-day figures, one row per ad
6187
+ ```
6188
+
6189
+ | Flag | Effect |
6190
+ |---|---|
6191
+ | `--slug <slug>` | Only this creative. |
6192
+ | `--days <n>` | Window ending today, 1-90 (default 30). |
6193
+ | `--full` | Adds creative and image ids, result type, days live, last-7-day CTR and cost per result, and one row per ad. |
6194
+
6195
+ Each creative reports `spend`, `impressions`, `ctr` (percent), `cpc`, `results`, `costPerResult`, `frequency`, a `fatigue` verdict (`fresh`, `fading`, `fatigued`, `dead`, `too_early`, `no_delivery`) with its reason, and `definition`: the angle, hook, mechanism, dominantSlot, persona, awareness, hypothesis and reference fields read from the local `src/creatives/<slug>/_definition.md`. `hints` name worn-out creatives, the angle, mechanism and dominant slot with the lowest cost per result (each ranked once two or more values have results), and unreadable ad accounts.
6196
+
6197
+ ### `baker creatives feedback [--slug <slug>] [--full]`
6198
+
6199
+ What reviewers said about creative versions in Baker (Approve, or Not this one with reasons and a note; choosing a version to keep counts as an approval). Compact by default: approved and rejected counts, the top rejection reasons, what approved versions share (format, platform, option) and the 8 latest verdicts, plus `hints[]` saying what to do differently. `--slug` narrows to one creative; `--full` returns every verdict (up to 200) with whole notes.
6200
+
6201
+ ```bash
6202
+ baker creatives feedback
6203
+ baker creatives feedback --slug spring-offer --full
6204
+ ```
6205
+
6206
+ Each entry carries its `source`: `review` (the buttons in Baker), `selection` (choosing or saving the version) or `chat` (what the person wrote in a chat, recorded with `feedback record`).
6207
+
6208
+ ### `baker creatives feedback record`
6209
+
6210
+ Record what the person said about one version in a chat: an approval, or a rejection with reasons and their own words. It is stored like the Approve / Not this one buttons, as the verdict of the person who wrote the chat's latest message (`recordedFor: "sender"`), or of the chat itself when nobody has written in it (`"chat"`). One verdict per person per version or option: a later one replaces theirs. Must run from a chat.
6211
+
6212
+ ```bash
6213
+ baker creatives feedback record --slug spring-offer --run r_01K… --verdict rejected \
6214
+ --reasons text_mistakes,weak_hook --note "the price is 49 not 59"
6215
+ baker creatives feedback record --slug spring-offer --run r_01K… --option 2 --verdict approved
6216
+ ```
6217
+
6218
+ | Flag | Effect |
6219
+ |---|---|
6220
+ | `--slug <slug>` | Required. The creative (`src/creatives/<slug>/`). |
6221
+ | `--run <r_…>` | Required. The version. |
6222
+ | `--verdict approved\|rejected` | Required. |
6223
+ | `--option <o>` | One option of a version that made several: its label, its number (`2`) or its slot. |
6224
+ | `--reasons <a,b>` | Comma-separated: `text_mistakes`, `logo_or_brand_wrong`, `weak_hook`, `wrong_message`, `looks_fake`, `not_our_style`, `other`. A rejection needs reasons or a note. |
6225
+ | `--note <text>` | The person's own words (stored up to 1000 characters). |
6226
+
6227
+ After a rejection, `hints[]` restates what to change, starting from that version.
6228
+
6229
+ ### `baker creatives select`
6230
+
6231
+ Choose the version of a creative to keep, or only some of its options, the way choosing it in Baker does: the creative shows that version as the chosen one, and the choice records the chat sender's approval (`source: "selection"`). Naming options replaces the earlier pick. It does not add the image to the library; that is `baker creatives publish`, which, given `--run-id`, also counts as choosing that version.
6232
+
6233
+ ```bash
6234
+ baker creatives select --slug spring-offer --run r_01K…
6235
+ baker creatives select --slug spring-offer --run r_01K… --options "beach,2"
6236
+ ```
6237
+
6238
+ Prints `{ ok, data: { creative, runId, kept?, options, approvalRecorded }, hints }`, where `options` lists every option the version offers.
6239
+
6126
6240
  ## Landing Quality (`baker landing`)
6127
6241
 
6128
6242
  Two tools that bracket a landing build: **`inspiration`** before you design (what does good look like here?) and **`critique`** after you build (did this settle into generic defaults?).