@koda-sl/baker-cli 0.421.0-dev.8ef74f67e → 0.422.0-dev.f9f855bf1
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 +40 -89
- package/dist/{chunk-EXXI274U.js → chunk-4RN4CBIQ.js} +3 -3
- package/dist/{chunk-QUFIEBEP.js → chunk-D76DSHL2.js} +25 -20
- package/dist/chunk-D76DSHL2.js.map +1 -0
- package/dist/chunk-FQ7WNNOG.js +11637 -0
- package/dist/chunk-FQ7WNNOG.js.map +1 -0
- package/dist/{chunk-6YAPMFMC.js → chunk-HFH5HQTP.js} +2 -2
- package/dist/{chunk-BGAU2ZQF.js → chunk-TTBMFAGF.js} +4 -4
- package/dist/{chunk-KBIEU3CK.js → chunk-VF77MVEM.js} +3 -3
- package/dist/cli.js +13523 -5213
- package/dist/cli.js.map +1 -1
- package/dist/{client-LYDB4ONI.js → client-OA2QMHOI.js} +4 -4
- package/dist/engine/index.d.ts +0 -8
- package/dist/engine/index.js +3 -3
- package/dist/{env-37XB6PGZ.js → env-Q5FUIVIJ.js} +2 -2
- package/dist/{output-DYBL2XBN.js → output-NMXF2VIL.js} +5 -5
- package/dist/{shared-XHOQLVMN.js → shared-PD226SU6.js} +6 -6
- package/package.json +1 -4
- package/dist/chunk-PFN3A4VQ.js +0 -24090
- package/dist/chunk-PFN3A4VQ.js.map +0 -1
- package/dist/chunk-QUFIEBEP.js.map +0 -1
- /package/dist/{chunk-EXXI274U.js.map → chunk-4RN4CBIQ.js.map} +0 -0
- /package/dist/{chunk-6YAPMFMC.js.map → chunk-HFH5HQTP.js.map} +0 -0
- /package/dist/{chunk-BGAU2ZQF.js.map → chunk-TTBMFAGF.js.map} +0 -0
- /package/dist/{chunk-KBIEU3CK.js.map → chunk-VF77MVEM.js.map} +0 -0
- /package/dist/{client-LYDB4ONI.js.map → client-OA2QMHOI.js.map} +0 -0
- /package/dist/{env-37XB6PGZ.js.map → env-Q5FUIVIJ.js.map} +0 -0
- /package/dist/{output-DYBL2XBN.js.map → output-NMXF2VIL.js.map} +0 -0
- /package/dist/{shared-XHOQLVMN.js.map → shared-PD226SU6.js.map} +0 -0
package/README.md
CHANGED
|
@@ -865,7 +865,7 @@ baker ads x targeting-constants --constant tv_shows --locale en-US
|
|
|
865
865
|
|
|
866
866
|
### ChatGPT Ads (`baker ads chatgpt`)
|
|
867
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
|
|
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 with an API key from its Ads Manager, pasted in the chat's connect card (`request_connection` `{ platform: "chatgpt-ads" }`) or in Brain → Integrations → ChatGPT Ads; the key stays in Baker and never reaches the CLI, so there is no account id to pass.
|
|
869
869
|
|
|
870
870
|
| Subcommand | What it returns |
|
|
871
871
|
|---|---|
|
|
@@ -950,10 +950,6 @@ baker ads meta ads --account-id act_123 --all-statuses # widen to
|
|
|
950
950
|
# "Find the October ad" — Meta matches the name, so only that row is read
|
|
951
951
|
baker ads meta ads --account-id act_123 --all-statuses --name-contains octubre --fields id,name,adset_id,creative_id
|
|
952
952
|
|
|
953
|
-
# "Who will see an ad in this campaign?" — a viewer per ad set: cold/warm/hot, what they
|
|
954
|
-
# already know (brand/offer/product), placements, and why — from targeting, audiences, objective
|
|
955
|
-
baker ads meta adsets --campaign-id 6123 --all-statuses --audience --output json
|
|
956
|
-
|
|
957
953
|
# "Tell me about ad 9988"
|
|
958
954
|
baker ads meta creatives --creative-id 9988
|
|
959
955
|
baker ads meta preview --creative-id 9988 --ad-format MOBILE_FEED_STANDARD --out-file /tmp/p.html
|
|
@@ -3291,7 +3287,7 @@ Search the **ad-dna** corpus of scored "winning" competitor ads for reference cr
|
|
|
3291
3287
|
|
|
3292
3288
|
### `baker winning-ads search <query>`
|
|
3293
3289
|
|
|
3294
|
-
Semantic search (dense recall + BM25 + rerank) → `POST /api/ad-library/winners/search`. The CLI projects each result to a **lean, decision-focused** shape so the agent's context stays small — default fields: `advertiser`, `advertiser_id`, `platform`, `format`, `relevance`, `winner_score`, `summary` (what the ad is about), `media_url`, and `visual` (what the viewer sees) + `container` (a static's layout pattern) + `mechanism` (why it works) when known; plus top-level `pool_size`, `match_confidence` (`high|medium|low`), and `below_floor_count` (matches dropped under the relevance floor). The proof signals ride along (`winner_category`, `days_active`, `active`)
|
|
3290
|
+
Semantic search (dense recall + BM25 + rerank) → `POST /api/ad-library/winners/search`. The CLI projects each result to a **lean, decision-focused** shape so the agent's context stays small — default fields: `advertiser`, `advertiser_id`, `platform`, `format`, `relevance`, `winner_score`, `summary` (what the ad is about), `media_url`, and `visual` (what the viewer sees) + `container` (a static's layout pattern) + `mechanism` (why it works) when known; plus top-level `pool_size`, `match_confidence` (`high|medium|low`), and `below_floor_count` (matches dropped under the relevance floor). The proof signals ride along (`winner_category`, `days_active`, `active`); `--full` adds DNA detail (`angle`, `target_persona`, `hook_archetype`, `awareness_stage`, `industry`) + `reach` and `media_kind`. By default the search draws from the brands the company follows first (`--scope followed-first`) and tops up from the shared library; every result says `source: followed|library`. `--output json` (default) returns the lean objects; `--output md` prints a table.
|
|
3295
3291
|
|
|
3296
3292
|
> `media_url` is the creative itself: for `static` it's the image, for `video` it's the video file. ad-dna stores **no separate poster** for videos, so a video result has only the video URL.
|
|
3297
3293
|
|
|
@@ -3308,8 +3304,6 @@ baker winning-ads search --ref-ad-id a_12345 --first-seen-after 2026-01-01T00:00
|
|
|
3308
3304
|
baker winning-ads search "meal kit for busy parents" --scope followed-first --diverse --limit 5 --output md
|
|
3309
3305
|
# The best proven ads in one container, from any category (no query needed):
|
|
3310
3306
|
baker winning-ads search --format-pattern comparison_table --platform meta --output md
|
|
3311
|
-
# References made for strangers that don't lean on their brand being known:
|
|
3312
|
-
baker winning-ads search "delivery driver recruitment" --audience-temperature 0_cold --exclude-brand-reliant --output md
|
|
3313
3307
|
```
|
|
3314
3308
|
|
|
3315
3309
|
| Flag | Purpose |
|
|
@@ -3325,10 +3319,6 @@ baker winning-ads search "delivery driver recruitment" --audience-temperature 0_
|
|
|
3325
3319
|
| `--format <list>` | `video,static,carousel` |
|
|
3326
3320
|
| `--winner-category <list>` | `winner,scaled_winner,evergreen,rising,untested,dud,…` (default: all) |
|
|
3327
3321
|
| `--awareness <list>` | `unaware,problem_aware,solution_aware,product_aware,most_aware` |
|
|
3328
|
-
| `--audience-temperature <list>` | Who the ad was made for: `0_cold,1_warm_engaged,2_hot_visited,3_customer` |
|
|
3329
|
-
| `--funnel-stage <list>` | `TOF,MOF,BOF` |
|
|
3330
|
-
| `--advertiser-size <list>` | `solo,smb,mid,enterprise` |
|
|
3331
|
-
| `--exclude-brand-reliant` | Drop ads whose copy only works if the viewer already knows the brand (ads not judged yet are kept) |
|
|
3332
3322
|
| `--advertiser-id <list>` | **Restrict to** these advertiser ids (browse one brand's winners) |
|
|
3333
3323
|
| `--exclude-advertiser <list>` | **Drop** these advertiser ids, usually your own brand |
|
|
3334
3324
|
| `--country <list>` / `--language <list>` | Filter by country / language codes |
|
|
@@ -3339,7 +3329,7 @@ baker winning-ads search "delivery driver recruitment" --audience-temperature 0_
|
|
|
3339
3329
|
| `--output json\|md\|files` | Output format (default json) |
|
|
3340
3330
|
| `--full` | Include DNA detail + longevity |
|
|
3341
3331
|
|
|
3342
|
-
A value outside a closed list (`--platform`, `--format`, `--winner-category`, `--awareness`, `--
|
|
3332
|
+
A value outside a closed list (`--platform`, `--format`, `--winner-category`, `--awareness`, `--format-pattern`), a non-numeric `--limit`/`--max-per-advertiser`/`--min-relevance`, or a bound that is not a date is refused before the search runs; a refused list value comes with `error.fix.validValues`. References this company already adapted (`already_adapted`) never take a fresh reference's place: `--limit N` returns up to N fresh ones first. The search waits up to 120 s (`followed-first` can run two library searches).
|
|
3343
3333
|
|
|
3344
3334
|
Reading the scores: **`relevance`** (0–1) = match of the creative to your query; **`winner_score`** = how proven the ad is in-market. Pick references that are both relevant *and* proven.
|
|
3345
3335
|
|
|
@@ -3740,12 +3730,13 @@ Permissions enforced server-side:
|
|
|
3740
3730
|
|
|
3741
3731
|
---
|
|
3742
3732
|
|
|
3743
|
-
### `baker mcp connected | list | add | remove`
|
|
3733
|
+
### `baker mcp connected | catalog | list | add | remove`
|
|
3744
3734
|
|
|
3745
3735
|
See everything a chat can reach — managed integrations, custom servers, and connected platforms — and manage **custom MCP servers** by pointing the agent at any remote HTTPS MCP endpoint. Registered tools appear as `mcp__<name>__*` on the next chat message.
|
|
3746
3736
|
|
|
3747
3737
|
```bash
|
|
3748
3738
|
baker mcp connected
|
|
3739
|
+
baker mcp catalog --search notion
|
|
3749
3740
|
baker mcp list
|
|
3750
3741
|
baker mcp add --name weather --url https://mcp.example.com/mcp
|
|
3751
3742
|
baker mcp add --name acme --url https://acme.dev/mcp --header "Authorization: Bearer sk-…"
|
|
@@ -3754,6 +3745,7 @@ baker mcp remove --name weather
|
|
|
3754
3745
|
```
|
|
3755
3746
|
|
|
3756
3747
|
- `connected` returns three lists. `managed` and `custom` are MCP tools the agent calls directly; `platforms` are the accounts the company signed in to (HubSpot, Google Ads, GA4, Search Console, Tag Manager, LinkedIn Ads), each with its login and the resources it may read. A platform has **no** `mcp__*` tools — it is read through its own command family — so it is listed here to keep the agent from concluding a working connection is missing.
|
|
3748
|
+
- `catalog --search <name> [--limit 1-20]` searches the managed apps a user can connect when a vendor has no MCP server of its own. Each app reports its slug (`app`), how the user connects it (`sign-in` or `key`; apps that need no sign-in cannot be connected and are left out) — never a managed copy of one of Baker's own connections: those come back under `platforms`, to ask for with `{ platform }` and whether this chat already reaches it (`connected`).
|
|
3757
3749
|
- `--url` must be the **full MCP endpoint** (commonly ending in `/mcp`), HTTPS and public; loopback / private / cloud-metadata hosts are rejected. A bare domain frequently accepts the OAuth sign-in and then serves no tools, so every address is handshake-checked after it changes: `connected` and `list` report `usable: false` plus a `problem` (and a `suggestedAddress` when the server advertises a better endpoint) for a server that isn't serving tools, or `lastCheck: failed` when the last check only hit an outage — that one may work anyway, and a later turn re-checks it automatically. Repointing a server at a different host clears its saved sign-in: a token belongs to the host that issued it, so the server has to be connected again.
|
|
3758
3750
|
- Scope is two independent choices — audience (just you / everyone) × level (this company / whole org):
|
|
3759
3751
|
- `--scope company` (default) — everyone in this company.
|
|
@@ -4092,9 +4084,9 @@ baker canvas validate my-canvas.json
|
|
|
4092
4084
|
# legal config to review before billing — a frame that describes a recurring element
|
|
4093
4085
|
# (a pet/product) but doesn't wire its reference (VIDEO_REFERENCE_MISSING), or a scene
|
|
4094
4086
|
# span that exceeds the assigned model's max clip duration (VIDEO_SPAN_EXCEEDS_MODEL),
|
|
4095
|
-
# or the same ad rendered once per aspect ratio instead of one hero +
|
|
4096
|
-
#
|
|
4097
|
-
#
|
|
4087
|
+
# or the same ad rendered once per aspect ratio instead of one hero + a terminal
|
|
4088
|
+
# image_aspect_adapt (IMAGE_PARALLEL_FORMAT_RENDERS — separate renders drift in type,
|
|
4089
|
+
# crop, and color, so the sizes stop matching).
|
|
4098
4090
|
# A generator prompt is measured with its {{slots}} filled from text nodes and local
|
|
4099
4091
|
# prompt.json/text ingests: past the model's character cap it is refused as
|
|
4100
4092
|
# PROMPT_OVER_MODEL_LIMIT, naming the node and the overshoot, before anything bills.
|
|
@@ -4196,11 +4188,10 @@ because a video model garbles a wordmark every time:
|
|
|
4196
4188
|
call to action when the spec has a `cta`; with neither a mark nor a `cta`, the last beat
|
|
4197
4189
|
keeps its footage. A typed stand-in (an SVG wordmark in live `<text>`) is still refused,
|
|
4198
4190
|
and `--no-brand` still means no brand layer at all: no mark and no closing card.
|
|
4199
|
-
- **Captions and the mark sit inside the safe area
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
in the same area. Every composition is sized to the spec's
|
|
4191
|
+
- **Captions and the mark sit inside the safe area.** On 9:16 the caption cards end above
|
|
4192
|
+
the bottom 37% of the frame (Reels covers the bottom 35%) and the mark starts below the
|
|
4193
|
+
top 16% (the Stories and Reels header); 4:5 and 1:1 use modest feed margins. The closing
|
|
4194
|
+
card is centred in the same area. Every composition is sized to the spec's
|
|
4204
4195
|
`format.aspect_ratio`, so a 4:5 ad lays its captions out on a 4:5 frame.
|
|
4205
4196
|
- **Captions and the closing button are set in the brand's face**: a font file in
|
|
4206
4197
|
`src/brand/fonts/`, else the typeface `tokens.json` or `BRAND.md` names, loaded as a web
|
|
@@ -4473,10 +4464,6 @@ Open record keyed by the composition's `meta.json` inputs.
|
|
|
4473
4464
|
|
|
4474
4465
|
---
|
|
4475
4466
|
|
|
4476
|
-
##### `static_prefilter`
|
|
4477
|
-
|
|
4478
|
-
Local; no credits (one box-reader call per variant, about $0.005, which rides on the run record's review spend). Ranks a static ad's variants before the judge by the deterministic checks (blocking defects, then every line of the ad spec's copy read back with its box, the brand's logo found, the button's words, every word and the logo inside the master's band) and passes on only the best-ranked, so the judge only breaks an exact tie. A variant the reader could not read ties with the best. **Inputs:** `images` (the variants' whole list), `spec` (the ad spec, `$ref:prompt.asset`). **Params:** `formats` (the delivered sizes). **Outputs:** `images` (the best-ranked variants, in an order seeded by the pictures rather than the render order; never empty: when every variant fails, the one with the fewest defects passes as `flagged`), `order` (which variant each of `images` is), `dropped` (each variant set aside, with its index and reasons), `flagged` (the variant kept when all failed) and `usage` (the reader's spend). Scaffolded by `scaffold-static-ad` between the variants and the judge.
|
|
4479
|
-
|
|
4480
4467
|
##### `font_specimen`
|
|
4481
4468
|
|
|
4482
4469
|
Render specimen text in a given font file to a **PNG** — black `#000` text on a white `#fff` background — via headless Chromium at 2× device-scale. Produces a **typeface reference image**: wire `image` into `image_generate`'s `reference` input so the model replicates the font when generating images that contain text.
|
|
@@ -4572,8 +4559,8 @@ None.
|
|
|
4572
4559
|
| `model` | enum | yes | one of `~google/gemini-flash-latest`, `~google/gemini-pro-latest` |
|
|
4573
4560
|
| `prompt` | string | yes | non-empty |
|
|
4574
4561
|
| `system` | string | no | system prompt |
|
|
4575
|
-
| `response_format` | enum | no | `text` (default) or `json_object`. Set `json_object` when a downstream `{{slot}}` consumes the output as JSON (e.g.
|
|
4576
|
-
| `web_search` | boolean | no | When `true`, routes through OpenRouter's `:online` web plugin so the model searches the live web before answering. Use it on
|
|
4562
|
+
| `response_format` | enum | no | `text` (default) or `json_object`. Set `json_object` when a downstream `{{slot}}` consumes the output as JSON (e.g. the ad-blueprint transform) so the model returns clean JSON with no markdown fences or prose. |
|
|
4563
|
+
| `web_search` | boolean | no | When `true`, routes through OpenRouter's `:online` web plugin so the model searches the live web before answering. Use it on the ad-blueprint transform so it adapts copy to the target brand's real facts (current pricing, the trust signals it actually has) instead of guessing. Adds web-search cost. |
|
|
4577
4564
|
| `temperature` | number | no | 0–2 |
|
|
4578
4565
|
| `max_tokens` | number | no | positive int |
|
|
4579
4566
|
|
|
@@ -4611,7 +4598,7 @@ Generate images via OpenRouter. The Zod schema is a permissive union; per-model
|
|
|
4611
4598
|
| `reference` | image **or** image[] | no | `image/png`, `image/jpeg`, `image/webp`, `image/gif` |
|
|
4612
4599
|
|
|
4613
4600
|
`reference` accepts a single image or an **array** of images. Wire several to combine references in one
|
|
4614
|
-
generation — e.g. a subject reference sheet + a
|
|
4601
|
+
generation — e.g. a subject reference sheet + a font specimen + the original ad. Each image is
|
|
4615
4602
|
forwarded to the model as a separate input, in array order (the provider accepts multiple). Wire an
|
|
4616
4603
|
array as a literal list: `"reference": ["$ref:subject.sheet", "$ref:type.image"]`.
|
|
4617
4604
|
|
|
@@ -4621,9 +4608,7 @@ array as a literal list: `"reference": ["$ref:subject.sheet", "$ref:type.image"]
|
|
|
4621
4608
|
|---|---|---|
|
|
4622
4609
|
| `images` | image[] | `image/png \| image/jpeg \| image/webp` (provider-determined) |
|
|
4623
4610
|
|
|
4624
|
-
**Cost** — 5 credits estimate per
|
|
4625
|
-
|
|
4626
|
-
**Several candidates from one call** — `n` (integer, default 1) asks one call for several images, returned together in `images`. Each image is billed as if it were its own call (`n: 3` costs three images); what it saves is requests and waiting. Only models that declare it take it (today `openai/gpt-image-2.5-sunburst`, up to 10); any other model refuses it before anything is billed. Wire the whole array into `image_select` to keep the best.
|
|
4611
|
+
**Cost** — 5 credits estimate per call (backend charges actual OpenRouter usage).
|
|
4627
4612
|
|
|
4628
4613
|
> `seed` is not supported — OpenRouter's `image_config` schema has no seed slot for image-gen.
|
|
4629
4614
|
|
|
@@ -4794,7 +4779,7 @@ Guidance:
|
|
|
4794
4779
|
|
|
4795
4780
|
Adapt ONE creative into multiple aspect ratios in a single step — the standard fan-out when a hero creative must ship to every placement (Meta: 9:16 stories, 1:1 feed, 4:5, 16:9…). The AI recomposes the layout per format: identical subject, faces, products, text (verbatim, same typography), logos, colors, and style; the scene is extended/restructured for the new canvas — never stretched, cropped, or letterboxed.
|
|
4796
4781
|
|
|
4797
|
-
The adaptation prompt is baked into the backend and is direction-aware
|
|
4782
|
+
The adaptation prompt is baked into the backend and is direction-aware (a wider target gets horizontal recomposition guidance, a taller one vertical). Formats that already match the source ratio (within ~2%) pass the source asset through unchanged — no model call, no cost.
|
|
4798
4783
|
|
|
4799
4784
|
**Inputs**
|
|
4800
4785
|
|
|
@@ -4936,13 +4921,13 @@ Search the web for real images instead of generating them. A backend LLM agent p
|
|
|
4936
4921
|
|
|
4937
4922
|
##### `image_select`
|
|
4938
4923
|
|
|
4939
|
-
Pick the best `count` images out of
|
|
4924
|
+
Pick the best `count` images out of 2+ candidates with a vision LLM, judged against a prompt. The output is a passthrough subset of the input refs — no new pixels are generated, so selection is cheap. Use it after fanning out several `image_generate` variants (or an `image_search` batch) to keep only the strongest before expensive downstream steps.
|
|
4940
4925
|
|
|
4941
4926
|
**Inputs**
|
|
4942
4927
|
|
|
4943
4928
|
| Slot | Kind | Required | Accepted MIMEs |
|
|
4944
4929
|
|---|---|---|---|
|
|
4945
|
-
| `images` | image[] | yes — at least
|
|
4930
|
+
| `images` | image[] | yes — at least 2 | `image/png`, `image/jpeg`, `image/webp`, `image/gif` |
|
|
4946
4931
|
|
|
4947
4932
|
Wire either a whole upstream array (`"$ref:gen.images"`) or a literal list collecting refs from several nodes (`["$ref:a.images#0", "$ref:b.images#0"]`).
|
|
4948
4933
|
|
|
@@ -4953,7 +4938,7 @@ Wire either a whole upstream array (`"$ref:gen.images"`) or a literal list colle
|
|
|
4953
4938
|
| `model` | enum | yes | one of `~google/gemini-flash-latest`, `~google/gemini-pro-latest` (`pro` for harder aesthetic judgement) |
|
|
4954
4939
|
| `prompt` | string | yes | selection criteria, e.g. `"most professional product shot, clean background"` |
|
|
4955
4940
|
| `count` | int | no | images to select, ≥ 1 (default 1); must not exceed the wired image count |
|
|
4956
|
-
| `temperature` | number | no | 0–2
|
|
4941
|
+
| `temperature` | number | no | 0–2, defaults to 0 |
|
|
4957
4942
|
| `max_tokens` | number | no | positive int |
|
|
4958
4943
|
|
|
4959
4944
|
**Outputs**
|
|
@@ -4961,7 +4946,7 @@ Wire either a whole upstream array (`"$ref:gen.images"`) or a literal list colle
|
|
|
4961
4946
|
| Slot | Kind | MIME |
|
|
4962
4947
|
|---|---|---|
|
|
4963
4948
|
| `images` | image[] | per input — exactly `count` refs, in the model's preference order, so `images#0`…`images#count-1` are always safe to wire |
|
|
4964
|
-
| `reasoning` | text | `text/plain` — why the winners won, compared against the rejected candidates
|
|
4949
|
+
| `reasoning` | text | `text/plain` — why the winners won, compared against the rejected candidates |
|
|
4965
4950
|
|
|
4966
4951
|
**Cost** — 1 credit estimate per call (backend charges actual OpenRouter usage).
|
|
4967
4952
|
|
|
@@ -5607,7 +5592,7 @@ Cache policies (via `--cache-policy`):
|
|
|
5607
5592
|
|
|
5608
5593
|
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.
|
|
5609
5594
|
|
|
5610
|
-
**The canvas file is brought up to date first.** Parts of a creative's canvas are projections of its blueprint: each video scene's frame and clip prompts (from `scenes/sNN.json`), a static ad's
|
|
5595
|
+
**The canvas file is brought up to date first.** Parts of a creative's canvas are projections of its blueprint: each video scene's frame and clip prompts (from `scenes/sNN.json`), a static ad's copy block and type specimen (from `prompt.json`), and the derived style file the frames read (`prompt.style.json`). `validate`, `run`, `critique` and `set-prompt` share one preflight that applies every projection and **writes the result back to the canvas file** (only when something changed; `canvas_updated` says so; `sync-definition`, which the file watcher runs over every creative, applies it without writing), so the file on disk, the graph the dashboard shows, the graph the critic scores and the graph that renders are the same. A scene edit that changes timing or the number of scenes is structural and still asks for a re-scaffold. Every preflight failure prints `{ ok: false, error: { code, message, fix? } }` and exits 2.
|
|
5611
5596
|
|
|
5612
5597
|
```bash
|
|
5613
5598
|
$ baker canvas validate my-canvas.json
|
|
@@ -5667,27 +5652,6 @@ $ baker canvas critique my-static-ad.json
|
|
|
5667
5652
|
"dimensions": [ { "dimension": "text_legibility_risk", "score": 1, "note": "2 copy block(s) — reads cleanly" }, … ] }
|
|
5668
5653
|
```
|
|
5669
5654
|
|
|
5670
|
-
#### `baker canvas viewer-check <slug>`
|
|
5671
|
-
|
|
5672
|
-
Would the person a static ad reaches understand it? A model plays that viewer — what they already know and
|
|
5673
|
-
where they scroll — and says, from `prompt.json`'s copy and scene alone, what is offered, to whom and what to do
|
|
5674
|
-
next; the answer is compared with `prompt.json`'s `message`. The ad fails when the viewer can only guess what is
|
|
5675
|
-
offered, or, for a viewer who does not know the brand, when the words say they already did something with it (a
|
|
5676
|
-
cart, a trial, a donation, a registration); who it is for is reported but never fails an ad alone. The viewer is the `viewer` recorded in
|
|
5677
|
-
`_definition.md`, else the one its `targetAdSet` reaches (as `baker ads meta adsets --audience` reads it), else
|
|
5678
|
-
the persona and `awareness` from the Knowledge, else a cold stranger. Not understood → exit 2 with
|
|
5679
|
-
`viewer_check_failed` and a `fix` naming what the viewer missed. Answers are cached per copy, scene and viewer.
|
|
5680
|
-
`baker canvas run` makes the same check on a static ad before it spends (skipped for `--fix` and resumed runs;
|
|
5681
|
-
`--skip-viewer-check` renders without it; a model outage lets the render through with a warning). `--full` adds
|
|
5682
|
-
the viewer's whole answer and how the viewer was chosen.
|
|
5683
|
-
|
|
5684
|
-
```bash
|
|
5685
|
-
$ baker canvas viewer-check club-whiteboard
|
|
5686
|
-
{ "ok": false, "error": { "code": "viewer_check_failed",
|
|
5687
|
-
"message": "Not rendered: a cold viewer who knows nothing about the brand yet, on automatic placements would not understand this ad.",
|
|
5688
|
-
"fix": "Seen by a cold viewer …, the words and picture alone do not say what is offered. …", "missed": [ … ] } }
|
|
5689
|
-
```
|
|
5690
|
-
|
|
5691
5655
|
#### `baker canvas run <file.json> [flags]` / `baker canvas run --slug <slug> [flags]`
|
|
5692
5656
|
|
|
5693
5657
|
Validate, then execute the graph. Blocks until done. Logs one line per node. Returns `{ ok, run_id, outputs_dir, output, stats }` on stdout.
|
|
@@ -5702,13 +5666,7 @@ A completed render the review calls ready (`verdict` `ready` or `ready_with_note
|
|
|
5702
5666
|
| `--force-remote` | off | With `--slug`: restore the recorded files even over local ones. |
|
|
5703
5667
|
| `--run-id <id>` | auto ULID | Override the generated run id. |
|
|
5704
5668
|
| `--cache-policy <policy>` | `read_write` | `read_write`, `bypass`, or `read_only`. |
|
|
5705
|
-
| `--concurrency <n>`
|
|
5706
|
-
| `--fresh` | off | Start a new run id instead of resuming an interrupted run of this canvas. |
|
|
5707
|
-
| `--regenerate <ids\|roles>` | none | Force these nodes, and everything downstream, fresh this run only. A role names every node carrying it: on a static ad `static_variants` (or its id `gen`) renders the variants again. An unknown name exits 2 (`unknown_regenerate_node`). |
|
|
5708
|
-
| `--fix "<what to change>"` | none | Static ad only: correct a finished version with one render that changes only what you say; every size is made again from it (see below). One render of the round. |
|
|
5709
|
-
| `--use-variant <sha256>` | none | Static ad only: make every size again from another of a version's variants, the one the person chose; the only requests re-lay it out for each other size (see below). |
|
|
5710
|
-
| `--from <run_id>` | the last render | With `--fix` or `--use-variant`: the version to start from. |
|
|
5711
|
-
| `--skip-viewer-check` | off | Static ad only: render without the viewer check (`baker canvas viewer-check`), when the person heard what the viewer would miss and wants the copy as it is. |
|
|
5669
|
+
| `--concurrency <n>` | `5` (or `BAKER_CANVAS_CONCURRENCY`) | Max nodes executing at once within a layer. |
|
|
5712
5670
|
| `--remote-cache <on\|off>` | `on` (or `BAKER_CANVAS_REMOTE_CACHE`) | Operator only. Company-scoped remote cache + durable asset persistence. |
|
|
5713
5671
|
| `--no-record` | records | Skip posting the durable run-history record (and its live progress). |
|
|
5714
5672
|
| `--until frames` | none | Stop before the clips with the frames settled; the run is recorded as `awaiting_approval` (not failed, not interrupted) and resumes with `--run-id`. |
|
|
@@ -5720,10 +5678,6 @@ A completed render the review calls ready (`verdict` `ready` or `ready_with_note
|
|
|
5720
5678
|
|
|
5721
5679
|
**Failures don't abandon sibling work.** Nodes in a layer run under the concurrency cap and every one **settles** — a failed clip no longer kills its in-flight siblings, whose finished results still land in the content-addressed cache. One failure re-throws as-is; several are reported together (each failed node named). Re-running `baker canvas run` resumes from the cache and re-executes **only** the failed nodes and their descendants — never hand-orchestrate per-node renders. **One render per canvas at a time:** a `run` of a canvas that another live process in the same workspace is still rendering (a backgrounded render, or a subagent's) is refused with `CONFLICT` instead of sharing its run, `--run-id` included — wait for it and use its result. Long `video_generate` clips execute as **backend jobs** (the CLI polls; a CDN/proxy timeout can no longer kill a generation mid-flight).
|
|
5722
5680
|
|
|
5723
|
-
**Resuming an interrupted run.** A long `baker canvas run` (multi-clip video) that is killed mid-render — session end, sandbox pause — leaves a marker under the outputs dir. The next `baker canvas run` of the same canvas automatically **resumes** that run: it reuses the run id so still-running billed jobs re-attach instead of being abandoned and re-billed, and completed nodes come from the cache. A live render keeps its marker's heartbeat fresh, so a second `run` of the same canvas is refused while it is alive (checked with `kill(pid, 0)` plus the heartbeat, on Linux and macOS alike). Resume also works from a **different workspace or a fresh sandbox**: when no local marker survives, the run history is consulted for a resumable run of the same source (canvas, `prompt.json`, scenes, compositions and formats), and it is adopted when the backend says it may be: interrupted, stopped at a checkpoint, or a `running` row whose keepalive is older than the backend's staleness threshold. Ctrl-C / SIGTERM aborts gracefully — no new nodes dispatch, a resumable snapshot is flushed, and the marker survives. A clean completion (or a handled failure) clears the marker, so a normal re-run starts a fresh generation. Force a new run with `--fresh`, or pin a specific run with `--run-id <id>` (also the escape hatch to adopt a run that is reported as concurrently live). Independent same-layer nodes (e.g. video clips) fan out in parallel up to `--parallel`/`--concurrency` (default 8; env `BAKER_CANVAS_CONCURRENCY`).
|
|
5724
|
-
|
|
5725
|
-
**Static ads: correct a version, or use another of its variants.** Correct a finished version with `--fix "<what to change>"`: one render takes that version (the last one, or `--from <run_id>`) as its first reference and changes only what you said, keeping the picture, the layout and every other word; it replaces the variants and the judge, every size is made again from the corrected picture, and it counts as one render of the round. Use another variant with `--use-variant <sha256>` (`--from <run_id>`, default the last render): when the person chose another of a version's variants over the judge's pick, every size is made again from that picture, and the only requests re-lay it out for each other size; `--regenerate adapt_9x16` lays out one size again from the same chosen picture when only that size came out wrong; `baker creatives feedback --slug <slug>` prints the choice (`variantChoice`) and the exact command.
|
|
5726
|
-
|
|
5727
5681
|
#### `baker canvas scaffold-video <video> [flags]`
|
|
5728
5682
|
|
|
5729
5683
|
Turn a reference video into a **runnable, self-validated reproduction canvas** in one command — the video counterpart of `scaffold-static-ad`. The `<video>` positional is a **local path OR an http(s) URL** (a `baker winning-ads` `media_url`, a library URL, any reel link) — a URL is downloaded for you (no manual `curl` first), so pass `--slug`/`--out` with it to give the canvas a home. It runs **billed passes** up front:
|
|
@@ -5833,32 +5787,29 @@ The two scaffold passes are billed (the full `video_deconstruct` is the heavy on
|
|
|
5833
5787
|
|
|
5834
5788
|
Turn a source/inspiration image into a **runnable, self-validated static-ad canvas** — the static counterpart of `scaffold-video`. It runs **billed Gemini passes** up front:
|
|
5835
5789
|
|
|
5836
|
-
1. **`image_describe`** (`~google/gemini-pro-latest`) — reverse-engineers the image
|
|
5837
|
-
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`). The describe pass also records the ad's **typography** (each typeface's classification, a best-guess family, and its weight/case)
|
|
5790
|
+
1. **`image_describe`** (`~google/gemini-pro-latest`) — reverse-engineers the image into a blueprint JSON, written next to the canvas as **`prompt.json`**. This is the editable "prompt": you rewrite it by hand into the ad you want (palette, copy, claims, subjects). It feeds the generator directly — there is **no automatic brand-transform step**. The blueprint also names the **`winning_mechanisms`** — the special sauce that makes the ad a candidate winner, each tagged `kind` (verbal: rhyme/pun/rhythm; visual: unexpected crop, visual gag, juxtaposition, pattern interrupt, before/after; structural: hook order/reveal) with a `device` and `why_it_works` — so your rewrite rebuilds the mechanism that makes the ad win instead of adapting only the surface and losing it. Every line of `text_content` carries its `surface` (`overlay` ad copy, or `in_scene`: written on a phone screen, a note, a sign, packaging) and `placement`.
|
|
5791
|
+
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`). 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). 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.
|
|
5838
5792
|
|
|
5839
5793
|
It then scaffolds a canvas on which **the image model designs the finished ad**, holding only what `prompt.json` holds; no element is mandatory:
|
|
5840
5794
|
|
|
5841
|
-
- Up to **three
|
|
5842
|
-
-
|
|
5843
|
-
-
|
|
5844
|
-
-
|
|
5845
|
-
- Detected elements get **one `[TODO]` ingest slot** each (never the logo), labelled by role (`el_product_1`, `el_person_1`), never by the source's brand. The original image stays on the canvas as the record of the source and is **never given to the render**: the generator copies what it is shown, whatever the prompt says. Each **animal hero** is fused into a generated **multi-view reference sheet** (`image_reference_sheet`) that the render grounds on; pass `--skip-actor-sheets` to ground 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.
|
|
5795
|
+
- Up to **three candidates** (`--takes`, default 3) render on `openai/gpt-image-2.5-sunburst` at quality `xhigh`, each a finished ad: the picture and every word of the copy set in type. The prompt quotes every string of `text_content` verbatim (never the logo's text) and asks for each once; the block is re-read from `prompt.json` on every `validate` and `run`, so an edit to the copy re-keys exactly the renders it touches. The brand's **typeface** is shown to the model as a `font_specimen` set on the ad's own copy: a file in `src/brand/fonts/` (via `src/brand/tokens.json` when present), else the heading family `tokens.json` or `BRAND.md` names, fetched as a web font file; when the brand names none, the model follows the blueprint's `fonts` and no neutral default is put in front of it. **The logo is optional**: the real file in `src/brand/logos/` goes to the model as a reference to reproduce, only when `prompt.json` keeps an `advertiser_brand` entry in `brands_logos` and the file exists; no logo is never an error, never a slot to fill and never a stand-in. Deleting that entry from `prompt.json` drops the logo on the next run; adding one to a canvas scaffolded without the file needs the file in `src/brand/logos/` and a re-scaffold. `prompt.json` also carries a **`brand`** block (ground, ink, accent, CTA and palette, from `src/brand/tokens.json` roles or `BRAND.md`), and the render binds its colours to it over the reference ad's. A 9:16 is told to keep every word, logo and button out of the Stories interface.
|
|
5796
|
+
- An `image_select` **judge** scores the candidates on one dominant idea, thumbnail legibility, exact words, layout (nothing overlaps or is clipped), mechanism kept, brand with the logo covered and artifacts, and picks the highest total among those whose words are exact.
|
|
5797
|
+
- Every other placement format is the winner **re-laid** by the same model from the ad itself (one billed render each, the winner as its first reference), so the set stays one ad. A format the model has no shape for (gpt-image has no 4:5) renders at its nearest shape (3:4) and is cropped exact by a free `imagemagick` node; the render prompt keeps every word 6% from the edges for it.
|
|
5798
|
+
- 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. Each **animal hero** is fused into a generated **multi-view reference sheet** (`image_reference_sheet`) that the render grounds on; pass `--skip-actor-sheets` to ground 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.
|
|
5846
5799
|
|
|
5847
5800
|
The canvas is validated before it is written. stdout returns `{ ok, canvas_path, prompt_path, models, aspect_ratio, placements, output_formats, stats, checklist }`; the **checklist** carries `summary` (what a run does: candidates, judge, type, logo, formats), `font`, `logo`, `in_scene_text`, `formats` and every real asset to drop in.
|
|
5848
5801
|
|
|
5849
|
-
**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 shape the
|
|
5802
|
+
**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 shape the candidates really render at (the model's nearest to the lead format), and `format_plan` says how each format is made (`render` or `relay`, and whether it is cropped exact).
|
|
5850
5803
|
|
|
5851
|
-
**
|
|
5804
|
+
**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; **every string of `prompt.json`'s copy transcribed off each format and compared character for character** (case and accents kept; a string read differently or not found is blocking on its own, and a format the model transcribed nothing for is reported as unread, never passed); and one vision pass for clipping, overlaps, artifacts, the Stories safe zones on a 9:16, one dominant idea, 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-candidate 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; every fix is an edit to `prompt.json` and a re-run (wrong words: the strings in `text_content`; wrong picture: the scene; a bad roll: `--regenerate ad_a`). **Render budget:** a static ad gets 3 renders per creative in a chat before the person reacts; the fourth exits 2 with `RENDER_BUDGET` without rendering, and `baker creatives feedback record` or `baker creatives select` on that creative opens a new round. Resuming an interrupted run does not count again, and a run that died only on an outage (timeout, network, a provider that says retry) gives its render back.
|
|
5852
5805
|
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
The render prompt is **model-aware**: each model's profile (`src/engine/lib/prompt-profiles.ts`) sets its shape, word budget, picture order and request params. gpt-image gets short labelled sections with the constraints last; Gemini gets sentences that give the reason and a positive closing list of the only words allowed. A profile change, or a new model on the router (`src/engine/lib/image-model-router.ts`), is re-run on the static bench (`scripts/creative-evals`, `--job static`) before it ships. Run `baker canvas critique` on the scaffolded canvas for an advisory read before you spend.
|
|
5806
|
+
The render 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. Run `baker canvas critique` on the scaffolded canvas for an advisory read before you spend.
|
|
5856
5807
|
|
|
5857
5808
|
**A reference from the winners library goes in by id.** `--source-ad-id <ad_id> --source-platform meta|linkedin|tiktok [--source-advertiser-id <id>]` (the ids beside the ad in `baker winning-ads search`) fetches the library's structured read of that ad, the same one `baker winning-ads content <ad_id> --full` returns, and writes it into `prompt.json` as a `reference` block: `{ ad_id, platform, advertiser, advertiser_id, format_pattern, layout: { composition, focal_subject, aspect_ratio, background, palette_mood, colors, production, aesthetic, cta_in_image, markers }, message: { mechanism, awareness, sophistication, angle, hook_archetype, pain_points, desires, value_props, objections_handled, proof, risk_reversal }, offer: { offer_type, pricing_model, entry_price, no_credit_card, guarantee, guarantee_days, urgency, bonus }, text_blocks: [{ text, role, position }], copy: { primary_text, headline, cta } }` (filled fields only). The describe pass still reads the pixels but is told that read as grounding, so the two cannot disagree on the container or the mechanism; when it names no `winning_mechanisms`, the library's container + mechanism seeds the list. The source ids (`sourceAdId`, `sourcePlatform`, `sourceAdvertiserId`, and `sourceAdvertiser` from the library when `--advertiser` is absent) are written to `_definition.md` and synced to the creative at scaffold time. A library read that fails warns, records the ids anyway and scaffolds from the picture alone (`hints`: `REFERENCE NOT READ`); with no picture either, the command exits 2 (`source_unavailable`). `baker canvas sync-definition` re-reads the three ids from `_definition.md` on every sync.
|
|
5858
5809
|
|
|
5859
5810
|
```bash
|
|
5860
|
-
baker canvas scaffold-static-ad --source-ad-id adg_123 --source-platform meta --slug spring-offer --platform meta
|
|
5861
|
-
# → the library's picture and read
|
|
5811
|
+
baker canvas scaffold-static-ad --source-ad-id adg_123 --source-platform meta --slug spring-offer --platform meta
|
|
5812
|
+
# → the library's picture and read; writes src/creatives/spring-offer/{spring-offer.canvas.json, prompt.json, _definition.md}
|
|
5862
5813
|
baker canvas scaffold-static-ad ./reference-ad.png --context "competitor ad for <brand>, <category>, <market>"
|
|
5863
5814
|
# → writes ./static-ad.canvas.json + ./prompt.json
|
|
5864
5815
|
# edit prompt.json into your ad + replace each [TODO] ingest path with a real file, then:
|
|
@@ -5876,8 +5827,8 @@ baker canvas run ./static-ad.canvas.json
|
|
|
5876
5827
|
| `--slug <slug>` | — | Creative slug (lowercase kebab): writes the canvas to `src/creatives/<slug>/<slug>.canvas.json` — the repo convention that attaches every run to the creative's dashboard generation history. `--out` wins over `--slug`. With a slug, the reference image is localized to durable storage and normalized to a model-safe format (SVG/AVIF/HEIC/… → PNG), so the canvas ingests a stable URL instead of an expiring one. |
|
|
5877
5828
|
| `--describe-model <id>` | registry default (`~google/gemini-pro-latest`) | Override the `image_describe` model. |
|
|
5878
5829
|
| `--select-model <id>` | registry default (`~google/gemini-flash-latest`) | Override the element-selection `text_generate` model. |
|
|
5879
|
-
| `--gen-model <id>` |
|
|
5880
|
-
| `--takes <n>` | `3` |
|
|
5830
|
+
| `--gen-model <id>` | `openai/gpt-image-2.5-sunburst` | Override the render model. An unknown model id in any `--*-model` flag fails the command (`unknown_model`) before anything is billed. |
|
|
5831
|
+
| `--takes <n>` | `3` | Finished ads rendered and judged (1-3); `1` skips the judge. |
|
|
5881
5832
|
| `--platform <channel>` | `meta` | `_definition.md` channel **and** the default placement set (`meta`\|`google`\|`linkedin`\|`tiktok`\|`youtube`\|`x`\|`other`; `x`/`other` deliver one format). |
|
|
5882
5833
|
| `--placements <preset\|none>` | derived from `--platform` | Override the set: `meta_feed`\|`meta_feed_stories`\|`meta_all`\|`linkedin`\|`google_demandgen`\|`tiktok`\|`youtube`, or `none` for a single base ad. |
|
|
5883
5834
|
| `--aspect <ratio>` | inferred from the image, else `9:16` | Force the lead format (it leads the placement set when the set has it). |
|
|
@@ -5886,6 +5837,8 @@ baker canvas run ./static-ad.canvas.json
|
|
|
5886
5837
|
|
|
5887
5838
|
Scaffolding runs (and bills) the two vision passes; **running** the result generates billed images. `baker canvas validate` does not check that the `[TODO]` paths exist — supply the real files before `run`.
|
|
5888
5839
|
|
|
5840
|
+
**Resuming an interrupted run.** A long `baker canvas run` (multi-clip video) that is killed mid-render — session end, sandbox pause — leaves a marker under the outputs dir. The next `baker canvas run` of the same canvas automatically **resumes** that run: it reuses the run id so still-running billed jobs re-attach instead of being abandoned and re-billed, and completed nodes come from the cache. A live render keeps its marker's heartbeat fresh, so a second `run` of the same canvas is refused while it is alive (checked with `kill(pid, 0)` plus the heartbeat, on Linux and macOS alike). Resume also works from a **different workspace or a fresh sandbox**: when no local marker survives, the run history is consulted for a resumable run of the same source (canvas, `prompt.json`, scenes, compositions and formats), and it is adopted when the backend says it may be: interrupted, stopped at a checkpoint, or a `running` row whose keepalive is older than the backend's staleness threshold. Ctrl-C / SIGTERM aborts gracefully — no new nodes dispatch, a resumable snapshot is flushed, and the marker survives. A clean completion (or a handled failure) clears the marker, so a normal re-run starts a fresh generation. **Correct a finished static ad** with `--fix "<what to change>"`: one render takes that version (the last one, or `--from <run_id>`) as its first reference and changes only what you said, keeping the picture, the layout and every other word; the other formats and the review follow, and it counts as one render of the round. Force a new run with `--fresh`, or pin a specific run with `--run-id <id>` (also the escape hatch to adopt a run that is reported as concurrently live). Independent same-layer nodes (e.g. video clips) fan out in parallel up to `--parallel`/`--concurrency` (default 8; env `BAKER_CANVAS_CONCURRENCY`).
|
|
5841
|
+
|
|
5889
5842
|
#### `baker canvas rerun <slug> [flags]` (deprecated)
|
|
5890
5843
|
|
|
5891
5844
|
Now `baker canvas run --slug <slug>`; `rerun` stays as a hidden alias for one release and prints a deprecation note. Every `canvas run` of a creative records a portable snapshot (the canvas JSON plus the local files it ingests: prompt blueprints, composition dirs, reference images) in durable storage, so `run --slug` can rebuild `src/creatives/<slug>/` in a workspace that never had it. A snapshot whose files are exactly the last one uploaded for the creative is reused without a request, and a file the store already holds is not uploaded again. Files the snapshot could not include (missing at run time, or oversized) are listed as warnings.
|
|
@@ -5897,7 +5850,7 @@ baker canvas run --slug spring-offer --force-remote --regenerate gen
|
|
|
5897
5850
|
|
|
5898
5851
|
#### `baker canvas stale`
|
|
5899
5852
|
|
|
5900
|
-
Lists the creatives edited since their last render in this workspace, each with the command to re-render it: `{ ok, data: { stale: [{ slug, canvasPath, renderedRunId
|
|
5853
|
+
Lists the creatives edited since their last render in this workspace, each with the command to re-render it: `{ ok, data: { stale: [{ slug, canvasPath, renderedRunId }] }, hints? }`. "Edited" is one content fingerprint of the creative's source: the canvas, `prompt.json`, every `scenes/sNN.json`, every local composition directory the canvas renders, and the `formats` in `_definition.md`, so a change counts however it was made. A completed `run` records the fingerprint it drew in `canvas/.rendered/<slug>.json`, and a render attempt of the current source is not asked for again until the source moves. Creatives never rendered in this workspace, and ones with an unfilled `[TODO]` asset slot, are not listed. Read-only, offline, free. The scaffold's turn-end reminder and its `validate-creative-renders` check read this list; the same fingerprint is what `sync-definition` and `run` post as `sourceSha`.
|
|
5901
5854
|
|
|
5902
5855
|
#### `baker canvas inspect <run_id> [--thumbnails]`
|
|
5903
5856
|
|
|
@@ -6427,8 +6380,6 @@ baker creatives feedback --slug spring-offer --full
|
|
|
6427
6380
|
|
|
6428
6381
|
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`).
|
|
6429
6382
|
|
|
6430
|
-
With `--slug`, `data.variantChoice` (`runId`, `option`, `sha256`) says which variant of a version the person chose over our pick in Baker, and the first hint is the command that makes every size from it (the only requests re-lay it out for each other size): `baker canvas run --slug <slug> --use-variant <sha256> --from <runId>`. Once a finished run made its sizes from that picture, the choice is used up and no longer returned.
|
|
6431
|
-
|
|
6432
6383
|
### `baker creatives feedback record`
|
|
6433
6384
|
|
|
6434
6385
|
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.
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
debugLogHttp,
|
|
3
3
|
readBodyForLog
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-HFH5HQTP.js";
|
|
5
5
|
import {
|
|
6
6
|
getEnv
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-D76DSHL2.js";
|
|
8
8
|
|
|
9
9
|
// src/client.ts
|
|
10
10
|
var MAX_RATE_LIMIT_RETRIES = 3;
|
|
@@ -208,4 +208,4 @@ export {
|
|
|
208
208
|
apiGet,
|
|
209
209
|
apiPost
|
|
210
210
|
};
|
|
211
|
-
//# sourceMappingURL=chunk-
|
|
211
|
+
//# sourceMappingURL=chunk-4RN4CBIQ.js.map
|
|
@@ -68,7 +68,8 @@ var PROXY_TIERS = {
|
|
|
68
68
|
port: 8e3,
|
|
69
69
|
usernamePrefix: "user-",
|
|
70
70
|
countryKey: "-country-",
|
|
71
|
-
sessionKey: null
|
|
71
|
+
sessionKey: null,
|
|
72
|
+
attempts: 3
|
|
72
73
|
},
|
|
73
74
|
residential: {
|
|
74
75
|
tier: "residential",
|
|
@@ -76,7 +77,8 @@ var PROXY_TIERS = {
|
|
|
76
77
|
port: 7777,
|
|
77
78
|
usernamePrefix: "customer-",
|
|
78
79
|
countryKey: "-cc-",
|
|
79
|
-
sessionKey: "-sessid-"
|
|
80
|
+
sessionKey: "-sessid-",
|
|
81
|
+
attempts: 1
|
|
80
82
|
}
|
|
81
83
|
};
|
|
82
84
|
var ESCALATION_ORDER = ["datacenter", "residential"];
|
|
@@ -274,10 +276,28 @@ function plannedRoutes(url, credentials, options) {
|
|
|
274
276
|
const routes = [DIRECT_ROUTE];
|
|
275
277
|
for (const tier of configuredTiers(credentials)) {
|
|
276
278
|
const built = buildProxyRoute(tier, credentials, options);
|
|
277
|
-
if (built.ok)
|
|
279
|
+
if (!built.ok) continue;
|
|
280
|
+
for (let attempt = 0; attempt < PROXY_TIERS[tier].attempts; attempt++) routes.push(built.route);
|
|
278
281
|
}
|
|
279
282
|
return routes;
|
|
280
283
|
}
|
|
284
|
+
function routeTier(route) {
|
|
285
|
+
return route.kind === "proxy" ? route.tier : "direct";
|
|
286
|
+
}
|
|
287
|
+
function nextPlannedRoute(routes, tried) {
|
|
288
|
+
const dead = new Set(tried.filter((attempt) => attempt.proxyFailure).map((attempt) => attempt.tier));
|
|
289
|
+
const spent = /* @__PURE__ */ new Map();
|
|
290
|
+
for (const attempt of tried) spent.set(attempt.tier, (spent.get(attempt.tier) ?? 0) + 1);
|
|
291
|
+
const offered = /* @__PURE__ */ new Map();
|
|
292
|
+
for (const route of routes) {
|
|
293
|
+
const tier = routeTier(route);
|
|
294
|
+
if (dead.has(tier)) continue;
|
|
295
|
+
const position = (offered.get(tier) ?? 0) + 1;
|
|
296
|
+
offered.set(tier, position);
|
|
297
|
+
if (position > (spent.get(tier) ?? 0)) return route;
|
|
298
|
+
}
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
281
301
|
|
|
282
302
|
// src/env.ts
|
|
283
303
|
import { createEnv } from "@t3-oss/env-core";
|
|
@@ -980,21 +1000,6 @@ function dnaMove(dna) {
|
|
|
980
1000
|
...mechanism ? { mechanism } : {}
|
|
981
1001
|
};
|
|
982
1002
|
}
|
|
983
|
-
function dnaAudience(dna) {
|
|
984
|
-
const text = (value) => typeof value === "string" && value ? value : void 0;
|
|
985
|
-
const audienceTemperature = text(dna.audience_temperature);
|
|
986
|
-
const funnelStage = text(dna.funnel_stage);
|
|
987
|
-
const advertiserSize = text(dna.advertiser_size);
|
|
988
|
-
const presumes = typeof dna.presumes_brand_knowledge === "boolean" ? dna.presumes_brand_knowledge : void 0;
|
|
989
|
-
const reason = presumes ? text(dna.brand_knowledge_reason) : void 0;
|
|
990
|
-
return {
|
|
991
|
-
...audienceTemperature ? { audience_temperature: audienceTemperature } : {},
|
|
992
|
-
...funnelStage ? { funnel_stage: funnelStage } : {},
|
|
993
|
-
...advertiserSize ? { advertiser_size: advertiserSize } : {},
|
|
994
|
-
...presumes !== void 0 ? { presumes_brand_knowledge: presumes } : {},
|
|
995
|
-
...reason ? { brand_knowledge_reason: reason } : {}
|
|
996
|
-
};
|
|
997
|
-
}
|
|
998
1003
|
function compactWinningAd(record) {
|
|
999
1004
|
const dna = asRecord(record.dna);
|
|
1000
1005
|
return {
|
|
@@ -1011,7 +1016,6 @@ function compactWinningAd(record) {
|
|
|
1011
1016
|
ad_id: String(record.ad_id ?? ""),
|
|
1012
1017
|
media_url: String(record.media_url ?? ""),
|
|
1013
1018
|
...dnaMove(dna),
|
|
1014
|
-
...dnaAudience(dna),
|
|
1015
1019
|
...bakerUses(record.baker_uses),
|
|
1016
1020
|
...record.source === "followed" || record.source === "library" ? { source: record.source } : {}
|
|
1017
1021
|
};
|
|
@@ -1347,6 +1351,7 @@ export {
|
|
|
1347
1351
|
refuseNonPublicUrl,
|
|
1348
1352
|
DIRECT_ROUTE,
|
|
1349
1353
|
plannedRoutes,
|
|
1354
|
+
nextPlannedRoute,
|
|
1350
1355
|
API_KEY_FORMAT_MESSAGE,
|
|
1351
1356
|
isBakerApiKey,
|
|
1352
1357
|
registerSchema,
|
|
@@ -1380,4 +1385,4 @@ export {
|
|
|
1380
1385
|
captureVimeoCookie,
|
|
1381
1386
|
childEnvWith
|
|
1382
1387
|
};
|
|
1383
|
-
//# sourceMappingURL=chunk-
|
|
1388
|
+
//# sourceMappingURL=chunk-D76DSHL2.js.map
|