@koda-sl/baker-cli 0.434.0-dev.0af4c01ba → 0.434.0-dev.3587df0fa
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 +7 -4
- package/dist/{chunk-EFRX2BLB.js → chunk-7NKAKF3K.js} +106 -11
- package/dist/chunk-7NKAKF3K.js.map +1 -0
- package/dist/cli.js +2725 -1740
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.d.ts +10 -0
- package/dist/engine/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-EFRX2BLB.js.map +0 -1
package/README.md
CHANGED
|
@@ -1834,7 +1834,8 @@ Ads start from what customers said. Three commands carry the process; the agent
|
|
|
1834
1834
|
```bash
|
|
1835
1835
|
baker creatives brief [--adset <id>] [--account-id act_<id>] [--no-research] # one read: viewer, account bottleneck, what works, taste, customer quotes, ideas on file
|
|
1836
1836
|
baker creatives ideas [--full] # check and rank src/creatives/_ideas/*.md; build_next
|
|
1837
|
-
baker creatives make <idea> [--slug <s>] [--adset <id>] [--reference <adId>] [--spec <beats.json>] [--from-idea]
|
|
1837
|
+
baker creatives make <idea> [--slug <s>] [--adset <id>] [--reference <adId>] [--spec <beats.json>] [--from-idea] [--series <lead>]
|
|
1838
|
+
baker creatives translate --slug <ad> --to es-MX,es-CO,pt-BR [--copy-file <json>] [--again] # the same ad in other languages, one design
|
|
1838
1839
|
baker creatives learn [--days <n>] [--dry-run] # what each idea's ads did, written back onto the idea
|
|
1839
1840
|
```
|
|
1840
1841
|
|
|
@@ -1842,6 +1843,8 @@ baker creatives learn [--days <n>] [--dry-run] # what each idea'
|
|
|
1842
1843
|
- `ideas` is free and local. An idea's `evidence` resolves to `src/content/insights/` entries, or it is an `explore` bet. Numbers must be in the Knowledge or our own customers' quotes (a Reddit, forum or web quote never sources a claim), no two ideas may share an angle and format, and a review or testimonial format needs a quote from our own customer.
|
|
1843
1844
|
- `learn` pools each built idea's creatives, judges them against the rest of the account's cost per result and writes `status` (`winner`, `failed`, `inconclusive`, `testing`) and a frozen `result` onto the idea. `ideas` then warns when a new idea repeats a failed one.
|
|
1844
1845
|
- `make` builds one idea. A static searches the idea's container for ads made for our viewer, checks the closest three with `winning-ads fit` and scaffolds from the first that fits. For a new creative it starts scaffolding the best-ranked one while the fits are asked (kept when it fits, stopped and removed whole when another or none is kept), so the reference's describe pass no longer waits for them. Finding and describing a reference gets 50 seconds; past that the build is stopped and the static starts from the idea (`REFERENCE_SLOW` in the hint). A creative whose ad is already written is never built over (`ALREADY_MADE`): edit its `prompt.json`, or pass `--slug` for another ad of the same idea. A video idea takes its beats spec. Either way it records the idea on `_definition.md`, sets `prompt.json`'s `message` and syncs the definition. Drafted copy lines claiming a figure the company never wrote come back first in `hints`. When no proven ad fits (or with `--from-idea`), a static starts from the idea itself: `prompt.json` holds the message and placeholders, and `canvas run` refuses to render until the scene and copy are written.
|
|
1846
|
+
- `make --series <lead>` makes a static idea as another ad of a series: several messages in one style. Nothing is searched or rendered; the ad's `_definition.md` names its lead (`series: <lead>`), its `prompt.json` starts as the lead's ready version with the idea's message, and only its `copy` is written. Its canvas is written from the lead's on every run: the lead's finished picture with this ad's words, every other size the lead's own size with the same words. `baker canvas run --series <lead>` renders every ad of the series at once, one reviewed and corrected run each, skipping an ad already ready from the lead's current version.
|
|
1847
|
+
- `translate --slug <ad> --to <langs>` makes the ad in other languages as ads of the series it leads: one creative per language or country variant, `<ad>-<lang>` (the BCP-47 tag in lowercase: `-es-mx`), whose `_definition.md` says `series: <ad>` and `language: <lang>` (canonical case, `es-MX`, as the spec's market and the dashboard store it; tags are compared without case) and whose `prompt.json` is the ad's as last drawn with its words translated. Every language is translated in one text call (transcreated, each regional tag in that country's own variety and form of address, `es-MX` and `es-CO` two ads, each line keeping its role and about its length, brand names, prices and numbers kept; the spend is billed with the workspace's next run), or read from `--copy-file` (`{"<lang>": ["line 1", …]}`) with no model call. The ad may itself be an ad of a series (an angle ad), so angles × languages stay one design. A language already made is left as it is, so adding one later re-renders nothing; `--again` translates it again. Then `baker canvas run --series <ad>` renders them; a new version of the ad reaches every language on the next `--series` run.
|
|
1845
1848
|
|
|
1846
1849
|
### Competitive Intelligence (`baker research`)
|
|
1847
1850
|
|
|
@@ -5918,7 +5921,7 @@ A completed render the review calls ready (`verdict` `ready` or `ready_with_note
|
|
|
5918
5921
|
| `--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). The same fix after it left its defect in place is refused (`no_progress`). |
|
|
5919
5922
|
| `--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). |
|
|
5920
5923
|
| `--from <run_id>` | the last render | With `--fix` or `--use-variant`: the version to start from. |
|
|
5921
|
-
| `--autofix` / `--no-autofix` | on | Static ad only: when the review finds a blocking defect, make up to
|
|
5924
|
+
| `--autofix` / `--no-autofix` | on | Static ad only: when the review finds a blocking defect, make up to 3 more renders (`AUTO_FIX_ROUNDS`) that correct it before reporting (see below). `--no-autofix` reports after the first render. |
|
|
5922
5925
|
| `--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. |
|
|
5923
5926
|
| `--remote-cache <on\|off>` | `on` (or `BAKER_CANVAS_REMOTE_CACHE`) | Operator only. Company-scoped remote cache + durable asset persistence. |
|
|
5924
5927
|
| `--no-record` | records | Skip posting the durable run-history record (and its live progress). |
|
|
@@ -5933,7 +5936,7 @@ A completed render the review calls ready (`verdict` `ready` or `ready_with_note
|
|
|
5933
5936
|
|
|
5934
5937
|
**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`).
|
|
5935
5938
|
|
|
5936
|
-
**Static ads: the run corrects a blocking defect itself.** When the review of a static ad finds a blocking defect, `baker canvas run` makes up to
|
|
5939
|
+
**Static ads: the run corrects a blocking defect itself.** When the review of a static ad finds a blocking defect, `baker canvas run` makes up to 3 more renders (`AUTO_FIX_ROUNDS` in `static-autofix.ts`) before it reports, correcting each defect at the layer that causes it: words or a logo where an interface covers them (Reels, Stories) or the frame cuts them are **placed and extended** (`withExtendedSizes`): a `static_place` node reads where everything sits and shrinks the ad (to 60% at most) into the size's safe area, and a `static_extend_merge` node has the ad's own model paint the margins that leaves and keeps its painting as one picture across the whole frame, laying the ad's own pixels back only over the words, logo and buttons the placing read (padded and feathered, so no edge of the shrunk ad shows; the whole ad is laid back, as before, when nothing was read or the painting moved the words), so the model is never asked to move anything (a size already inside its area is passed on with nothing painted or billed, and one whose words sit too far out for a shrink to clear, or that the placing found inside its area while the review still blocks it, is laid out again instead, never placed the same way twice; a button is placed by its shape, its words grown by 0.7 of their height on every side, as the review judges it); a defect the spec asked for (a second subject, stars with no named source, a concept that does not read) and any defect of a native screen corrects the spec first (`static-spec-repair.ts`: a model rewrites `prompt.json` for those findings alone, every line of the copy kept word for word), then draws the ad again from it (a native screen redraws free); a picture wrong as a whole or in three places or more is made again from its brief, told what the last one got wrong, but only in a round with another after it unless the brief caused the defect; anything else on the picture is a `--fix` of the chosen picture for its own findings (`correctionOf`: the words to erase, the line to re-letter or add), naming what the last edit was already asked for, while each re-laid size the review flagged follows as an edit of its own last layout (`withSizesFollowingFix`), keeping what an earlier round fixed in it, and a size it found nothing blocking on is delivered as it was (`keep`, nothing in it made again or placed); anything else on a re-laid size alone edits just that size from its own last layout (`withRelayEdit`); sizes that do not show the same ad (`SIZES:`) are always the re-laid ones' fault, since every size follows the chosen picture, so they are laid out again to show exactly what it shows, never the picture edited to match them. A size a round did not touch (its file the same byte for byte) keeps the round before's verdict (`carryUnchanged`) and is not read again: the review reads only the sizes the round changed. Each correction is told what earlier rounds fixed so it keeps it right (`keptRules`), and starts from the best attempt so far: one that came out with more blocking defects than an earlier attempt is set aside and the earlier one corrected instead (`correctFrom`). Each round is one render (a fix, or one request per re-laid size) and prints `[autofix] round N of 3: …` on stdout; the final JSON's hints start with "Corrected automatically before this result, …" and its review is of the corrected version. Blocking findings left after the rounds keep the hints below. A round that would only repeat the correction that just left its defect in place (a chosen option, `--use-variant`, is only ever corrected) is not run: that version is reported as it is. `--no-autofix` reports after the first render.
|
|
5937
5940
|
|
|
5938
5941
|
**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, and every size is made again from the corrected picture. **A `prompt.json` changed only in its words, their positions or its colours** since the version shown is made the same way, as an edit of that version (`[edit]` in the output, the instruction read off the change); three new options are asked for only with `--regenerate static_variants`. A `--fix` whose defect is still blocking afterwards is recorded, and asking for it again with the same words exits 2 with `no_progress`. When every blocking finding is on re-laid-out sizes, the first hint says to lay out those sizes again (`--regenerate adapt_<size>`) before any `--fix`. Every static run's hints say what it cost in credits and what for. There is no render limit per creative. 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 (after a `--fix` or `--use-variant`, from that corrected or chosen picture, so the size never brings back the uncorrected one; `--from <run_id>` names another version); `baker creatives feedback --slug <slug>` prints the choice (`variantChoice`) and the exact command.
|
|
5939
5942
|
|
|
@@ -6063,7 +6066,7 @@ The canvas is validated before it is written. stdout returns `{ ok, canvas_path,
|
|
|
6063
6066
|
|
|
6064
6067
|
**Who sees the ad.** `--target-adset <id>` names the Meta ad set the ad will run in. Before anything is billed, the viewer its targeting reaches is read exactly as `baker ads meta adsets --audience` reads it (`temperature`, what they already `knows`, `placements`) and written to `_definition.md` as `targetAdSet` and `viewer` (an existing definition keeps every other field). The sizes, the brief and the prefilter keep clear of that viewer's placements (`staticSafeArea`; `automatic`, a bare `facebook`/`instagram` or no placements is Advantage+, Reels included), and every `baker canvas run` checks the ad against that viewer. With no ad account, give the viewer from the Knowledge: `--viewer-temperature cold|warm|hot [--viewer-knows brand,offer,product] [--viewer-placements <placement,…>]`, recorded with `source: knowledge`; next to `--target-adset` they correct the read, and its `reasons` say so. A bad value exits 2 (`invalid_viewer`) before any billed pass; an ad set that cannot be read never stops the scaffold (a hint says why, and the sizes keep clear of every placement). With neither flag, a `viewer` (or `targetAdSet`) already in `_definition.md` is used as it is. stdout carries `target_adset`, `viewer` and a `checklist.viewer` line.
|
|
6065
6068
|
|
|
6066
|
-
**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 line of `prompt.json`'s copy (in-scene and designed alike) read back with its box by a box reader and compared** (a line more than 2% off, missing, or joined by words nobody asked for is blocking; case and punctuation fold, as does a capital I read as an l (one stroke in a sans-serif), accents do not; words the picture adds are blocking only when set at the copy's usual size or making a claim (a figure, a price, an offer), and otherwise a note, as a screen's code, a poster or a mug in the scene, except a price (a currency sign beside a figure) the copy does not hold, blocking at any size and blurred by the correction, the document it is on kept; every render is asked for no readable prices on a bill, quote, receipt, screen or price tag beyond the copy; a copy line is "on the ad twice" only when most of its words repeat, never a word or two echoed on a prop; on a native screen, which code typesets from the spec, a line read differently is the reader's error and only a missing one counts); **the ad's own copy and its logo measured against the format's safe area** (`staticSafeArea`, the one owner of those areas; `must-show.ts` decides what is measured: never what the scene prints, nor a copy line printed again in the scene while the line itself sits clear). On a 9:16, which runs in Stories and Reels alike, what Stories' header or bottom bar covers (and any other app's interface named) is said as "where Stories and Reels cover it" ("where Reels covers it" for an ad set that runs only in Reels) and blocks for the logo, the button, the headline, the figure and the offer, while a supporting line (body, caption, label, small print) there is a note; what only Reels' taller footer covers is one note, and only for the lead line, since Stories shows it and the 9:16 is laid out in Stories' band; words merely close to where an app draws are no finding. The steps that place a size clear of an interface (`static_safe_fit`, `static_place`) are told the same copy (`prompt.json`'s lines, their roles, and the brand's name and the logo's words, a wordmark read close to them counting as the logo) and move the ad for those alone, a supporting line included only where the frame cuts it; on a feed image such as a 4:5, only a design margin, so words close to the edge are a warning and only words the frame cuts are blocking); a copy line written over two lines (`"15%\noff"`) is matched part by part wherever the reader finds each; designed type in a colour far from the brand palette is a warning; supporting lines too small to read on a phone are one note per size; the main line set smaller than a supporting one is a note, the brand's wordmark never counted as part of a line that starts with its name; the logo set twice is a note, a far smaller mark printed on something in the scene (a hoodie, a bag) is not. When the copy names a real person ("Lautaro Suarez, CTO at Vibiz"), the run casts them as themselves before it renders (`baker avatars research --create`, their avatar's sheet added to `prompt.json` `references` as a `PERSON_n`), and a face shown as a named person the ad has no photo of is blocking. When the reader cannot answer, the vision pass transcribes the copy instead and judges the safe zones by eye. Then one vision pass for clipping, overlaps, artifacts, 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, and the run is recorded with `presentable: false` so it is never shown as a version; every fix is an edit to `prompt.json` and a re-run (wrong words: the lines in `copy`; wrong picture: the `scene`; a bad roll: `--regenerate static_variants`; one size wrong while the chosen picture is right: `--regenerate adapt_<size>`, which re-lays it out, named in the hints); a blocking defect is first corrected by the run itself (`--autofix`, above). The checks rank the variants before the pick and the judge only breaks an exact tie, comparing pairwise against the compiled brief, knowing the brand and the viewer; every variant stays on offer to the person in Baker. Within one chat turn an ad gets two runs you start (each with its automatic corrections) and about
|
|
6069
|
+
**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 line of `prompt.json`'s copy (in-scene and designed alike) read back with its box by a box reader and compared** (a line more than 2% off, missing, or joined by words nobody asked for is blocking; case and punctuation fold, as does a capital I read as an l (one stroke in a sans-serif), accents do not; words the picture adds are blocking only when set at the copy's usual size or making a claim (a figure, a price, an offer), and otherwise a note, as a screen's code, a poster or a mug in the scene, except a price (a currency sign beside a figure) the copy does not hold, blocking at any size and blurred by the correction, the document it is on kept; every render is asked for no readable prices on a bill, quote, receipt, screen or price tag beyond the copy; a copy line is "on the ad twice" only when most of its words repeat, never a word or two echoed on a prop; on a native screen, which code typesets from the spec, a line read differently is the reader's error and only a missing one counts); **the ad's own copy and its logo measured against the format's safe area** (`staticSafeArea`, the one owner of those areas; `must-show.ts` decides what is measured: never what the scene prints, nor a copy line printed again in the scene while the line itself sits clear). On a 9:16, which runs in Stories and Reels alike, what Stories' header or bottom bar covers (and any other app's interface named) is said as "where Stories and Reels cover it" ("where Reels covers it" for an ad set that runs only in Reels) and blocks for the logo, the button, the headline, the figure and the offer, while a supporting line (body, caption, label, small print) there is a note; what only Reels' taller footer covers is one note, and only for the lead line, since Stories shows it and the 9:16 is laid out in Stories' band; words merely close to where an app draws are no finding. The steps that place a size clear of an interface (`static_safe_fit`, `static_place`) are told the same copy (`prompt.json`'s lines, their roles, and the brand's name and the logo's words, a wordmark read close to them counting as the logo) and move the ad for those alone, a supporting line included only where the frame cuts it; on a feed image such as a 4:5, only a design margin, so words close to the edge are a warning and only words the frame cuts are blocking); a copy line written over two lines (`"15%\noff"`) is matched part by part wherever the reader finds each; designed type in a colour far from the brand palette is a warning; supporting lines too small to read on a phone are one note per size; the main line set smaller than a supporting one is a note, the brand's wordmark never counted as part of a line that starts with its name; the logo set twice is a note, a far smaller mark printed on something in the scene (a hoodie, a bag) is not. When the copy names a real person ("Lautaro Suarez, CTO at Vibiz"), the run casts them as themselves before it renders (`baker avatars research --create`, their avatar's sheet added to `prompt.json` `references` as a `PERSON_n`), and a face shown as a named person the ad has no photo of is blocking. When the reader cannot answer, the vision pass transcribes the copy instead and judges the safe zones by eye. Then one vision pass for clipping, overlaps, artifacts, 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, and the run is recorded with `presentable: false` so it is never shown as a version; every fix is an edit to `prompt.json` and a re-run (wrong words: the lines in `copy`; wrong picture: the `scene`; a bad roll: `--regenerate static_variants`; one size wrong while the chosen picture is right: `--regenerate adapt_<size>`, which re-lays it out, named in the hints); a blocking defect is first corrected by the run itself (`--autofix`, above). The checks rank the variants before the pick and the judge only breaks an exact tie, comparing pairwise against the compiled brief, knowing the brand and the viewer; every variant stays on offer to the person in Baker. Within one chat turn an ad gets two runs you start (each with its automatic corrections) and about 12 minutes in all: a correction round that would take it past that is not started (`[autofix] not started`), the version shown as it stands, and no render starts once it is spent or once the ad is `ready` or `ready_with_notes`: the next is refused (`CONFLICT`, exit 2) with what to show instead; the person's next message starts a new turn (`turn-renders.ts`). When a defect still stands after the automatic rounds, the hints say to fix it with a `--fix`, then by changing the spec or the approach, and if it still cannot be fixed to show the best version and name what is left in one plain sentence, without a question.
|
|
6067
6070
|
|
|
6068
6071
|
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.
|
|
6069
6072
|
|
|
@@ -4398,7 +4398,18 @@ var CanvasMetadata = z4.object({
|
|
|
4398
4398
|
// `scaffold-video`) so the editable checklist travels inside the canvas
|
|
4399
4399
|
// instead of only printing to stdout. Ignored by the engine.
|
|
4400
4400
|
todo: z4.unknown().optional(),
|
|
4401
|
-
video: VideoMeta
|
|
4401
|
+
video: VideoMeta,
|
|
4402
|
+
// A static ad of a series (`seriesMemberAd`): the lead ad its picture is an edit of, the
|
|
4403
|
+
// lead's version, and the lead's words as that version drew them, which this ad's replace.
|
|
4404
|
+
// Ignored by the engine; the brief projection reads it.
|
|
4405
|
+
series: z4.object({
|
|
4406
|
+
lead: z4.string().min(1),
|
|
4407
|
+
run_id: z4.string().min(1),
|
|
4408
|
+
copy: z4.array(z4.object({ text: z4.string(), role: z4.string().optional() }).strict()),
|
|
4409
|
+
design: z4.string().optional(),
|
|
4410
|
+
// A translation of the lead: the language (BCP-47) its words are in.
|
|
4411
|
+
language: z4.string().min(2).optional()
|
|
4412
|
+
}).strict().optional()
|
|
4402
4413
|
}).strict().optional();
|
|
4403
4414
|
var CanvasSchema = z4.object({
|
|
4404
4415
|
schema: z4.literal("baker-canvas/1"),
|
|
@@ -7464,7 +7475,7 @@ function pictureInstruction(p, ctx) {
|
|
|
7464
7475
|
const keep = p.spec?.keep ? ` Keep ${plain(p.spec.keep, ctx.clean)}.` : "";
|
|
7465
7476
|
switch (p.role) {
|
|
7466
7477
|
case "base":
|
|
7467
|
-
return ctx.job === "fix" ? "the current version of this ad, to edit." : "the finished ad, to re-lay for this shape.";
|
|
7478
|
+
return ctx.job === "fix" ? "the current version of this ad, to edit." : ctx.job === "series" ? "the finished ad this series repeats, to copy exactly with new words." : "the finished ad, to re-lay for this shape.";
|
|
7468
7479
|
case "identity":
|
|
7469
7480
|
return identityInstruction(p, what, keep, ctx.flowing);
|
|
7470
7481
|
// The user saw one label come back two ways (E4 gallery, BENCH.md § "E1 v2"): the label is the product's.
|
|
@@ -7843,6 +7854,10 @@ function deliverableSection(c) {
|
|
|
7843
7854
|
const change = sentence(c.opts.instruction?.trim() || "correct what is wrong");
|
|
7844
7855
|
return c.flowing ? `Image 1 is the current version of this ad. ${capitalize(change)} Keep everything else in Image 1 exactly the same, preserving its picture, layout, colours, lettering and every word.` : `Edit: Image 1 is the current version of this ad. ${capitalize(change)} Keep everything else exactly as it is in Image 1: the picture, the layout, the colours, the lettering and every word.`;
|
|
7845
7856
|
}
|
|
7857
|
+
if (c.job === "series") {
|
|
7858
|
+
const change = sentence(c.opts.instruction?.trim() || "set the new words");
|
|
7859
|
+
return c.flowing ? `Image 1 is a finished ad of this series. Make the next ad of the same series${forBrand}: ${change} Keep everything else in Image 1 exactly the same, preserving its picture, layout, background, shapes, colours, lettering style and logo, each word in the place and style of the one it replaces.` : `Edit: Image 1 is a finished ad of this series. Make the next ad of the same series${forBrand}. ${capitalize(change)} Keep everything else exactly as it is in Image 1: the picture, the layout, the background, the shapes, the colours, the lettering style and the logo, each new line in the place, size and style of the line it replaces.`;
|
|
7860
|
+
}
|
|
7846
7861
|
if (c.job === "relay") {
|
|
7847
7862
|
return c.flowing ? `Image 1 is a finished ad. Recompose this same ad as a ${noun}${forBrand}: the same picture, colours, lettering and every word, rearranged for the new shape, with more of the scene revealed at the edges.` : `Deliverable: the same ad as Image 1, re-laid as a finished ${noun}${forBrand}. Keep the same picture, colours, lettering and every word, rearranged for the new shape; show more of the scene rather than cropping it.`;
|
|
7848
7863
|
}
|
|
@@ -8513,6 +8528,7 @@ var STATIC_ADAPT_ROLE = "static_adapt";
|
|
|
8513
8528
|
var STATIC_SAFE_FIT_ROLE = "static_safe_fit";
|
|
8514
8529
|
var STATIC_PLACE_ROLE = "static_place";
|
|
8515
8530
|
var LOGO_COMPOSITE_ROLE = "logo_composite";
|
|
8531
|
+
var SERIES_LEAD_ROLE = "series_lead";
|
|
8516
8532
|
var STATIC_FIX_ROLE = "static_fix";
|
|
8517
8533
|
var LOGO_COMPOSITE_DEFAULT = false;
|
|
8518
8534
|
var ADAPT_IMAGE_SIZE = "2K";
|
|
@@ -8532,6 +8548,19 @@ function rolesById(canvas) {
|
|
|
8532
8548
|
}
|
|
8533
8549
|
return out;
|
|
8534
8550
|
}
|
|
8551
|
+
function relaidPictureRole(canvas) {
|
|
8552
|
+
const roles = rolesById(canvas);
|
|
8553
|
+
const final = nodesOf(canvas).find((n) => n.id === "final" && n.type === "collect");
|
|
8554
|
+
const labels = final?.params?.labels;
|
|
8555
|
+
const images = final?.inputs?.images;
|
|
8556
|
+
if (!Array.isArray(labels) || !Array.isArray(images)) return {};
|
|
8557
|
+
const at2 = images.findIndex((ref) => {
|
|
8558
|
+
const from = typeof ref === "string" ? /^\$ref:([^.]+)\./.exec(ref)?.[1] : void 0;
|
|
8559
|
+
return from !== void 0 && roles[from] === STATIC_MASTER_ROLE;
|
|
8560
|
+
});
|
|
8561
|
+
const format = labels[at2];
|
|
8562
|
+
return typeof format === "string" ? { [`last_${formatSlug(format)}`]: STATIC_MASTER_ROLE } : {};
|
|
8563
|
+
}
|
|
8535
8564
|
function formatSlug(format) {
|
|
8536
8565
|
return format.replace(":", "x").replace(".", "_");
|
|
8537
8566
|
}
|
|
@@ -16480,6 +16509,9 @@ var MIN_EXTEND_SCALE = 0.6;
|
|
|
16480
16509
|
var GREY_LEVEL = 128;
|
|
16481
16510
|
var GREY = { r: GREY_LEVEL, g: GREY_LEVEL, b: GREY_LEVEL };
|
|
16482
16511
|
var FEATHER = 0.012;
|
|
16512
|
+
var KEEP_PAD = 0.3;
|
|
16513
|
+
var KEEP_FEATHER = 0.03;
|
|
16514
|
+
var MOVED = 120;
|
|
16483
16515
|
function ratioOf2(ratio) {
|
|
16484
16516
|
const [w = 1, h = 1] = ratio.split(":").map(Number);
|
|
16485
16517
|
return w / h;
|
|
@@ -16542,14 +16574,16 @@ function clearBusyMargins(rgba, frame, keep) {
|
|
|
16542
16574
|
}
|
|
16543
16575
|
return shown;
|
|
16544
16576
|
}
|
|
16545
|
-
async function mergeExtended(extended, placed, ratio) {
|
|
16577
|
+
async function mergeExtended(extended, placed, ratio, keep = []) {
|
|
16546
16578
|
const ad = await sharp7(placed).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
16547
16579
|
const { width, height } = ad.info;
|
|
16548
16580
|
const bounds = placedBounds(ad.data, width, height);
|
|
16549
16581
|
if (!bounds) return sharp7(placed).removeAlpha().png().toBuffer();
|
|
16550
16582
|
const canvas = renderCanvas({ width, height }, ratio);
|
|
16551
16583
|
const paint = await sharp7(extended).removeAlpha().resize(canvas.width, canvas.height, { fit: "fill" }).extract({ left: canvas.left, top: canvas.top, width, height }).raw().toBuffer();
|
|
16552
|
-
const
|
|
16584
|
+
const edge = edgeWeight(bounds, width, height, Math.max(1, Math.round(FEATHER * width)));
|
|
16585
|
+
const boxes = keep.length > 0 && keptInPlace(keep, ad.data, paint, width, height) ? keptWeights(keep, width, height) : null;
|
|
16586
|
+
const weight = boxes ? (x, y) => Math.min(edge(x, y), boxes[y * width + x] ?? 0) : edge;
|
|
16553
16587
|
const out = Buffer.alloc(width * height * 3);
|
|
16554
16588
|
for (let y = 0; y < height; y++) {
|
|
16555
16589
|
for (let x = 0; x < width; x++) {
|
|
@@ -16588,18 +16622,65 @@ function edgeWeight(b, width, height, feather) {
|
|
|
16588
16622
|
ramp(b.y1 - y, b.y1 === height - 1)
|
|
16589
16623
|
);
|
|
16590
16624
|
}
|
|
16625
|
+
function keptWeights(keep, width, height) {
|
|
16626
|
+
const out = new Float32Array(width * height);
|
|
16627
|
+
const feather = Math.max(1, KEEP_FEATHER * width);
|
|
16628
|
+
for (const box of keep) {
|
|
16629
|
+
const pad = KEEP_PAD * (box.y1 - box.y0) * height;
|
|
16630
|
+
const x0 = box.x0 * width - pad;
|
|
16631
|
+
const x1 = box.x1 * width + pad;
|
|
16632
|
+
const y0 = box.y0 * height - pad;
|
|
16633
|
+
const y1 = box.y1 * height + pad;
|
|
16634
|
+
const ys = Math.max(0, Math.floor(y0 - feather));
|
|
16635
|
+
const ye = Math.min(height - 1, Math.ceil(y1 + feather));
|
|
16636
|
+
const xs = Math.max(0, Math.floor(x0 - feather));
|
|
16637
|
+
const xe = Math.min(width - 1, Math.ceil(x1 + feather));
|
|
16638
|
+
for (let y = ys; y <= ye; y++) {
|
|
16639
|
+
const dy = Math.max(0, y0 - y, y - y1);
|
|
16640
|
+
for (let x = xs; x <= xe; x++) {
|
|
16641
|
+
const dx = Math.max(0, x0 - x, x - x1);
|
|
16642
|
+
const t = Math.max(0, 1 - Math.hypot(dx, dy) / feather);
|
|
16643
|
+
const w = t * t * (3 - 2 * t);
|
|
16644
|
+
const i = y * width + x;
|
|
16645
|
+
if (w > (out[i] ?? 0)) out[i] = w;
|
|
16646
|
+
}
|
|
16647
|
+
}
|
|
16648
|
+
}
|
|
16649
|
+
return out;
|
|
16650
|
+
}
|
|
16651
|
+
function keptInPlace(keep, ad, paint, width, height) {
|
|
16652
|
+
const diffs = keep.map((box) => boxDiff(box, ad, paint, width, height)).filter((d) => d !== null).sort((a, b) => a - b);
|
|
16653
|
+
return (diffs[Math.floor(diffs.length / 2)] ?? Number.POSITIVE_INFINITY) <= MOVED;
|
|
16654
|
+
}
|
|
16655
|
+
function boxDiff(box, ad, paint, width, height) {
|
|
16656
|
+
let sum = 0;
|
|
16657
|
+
let n = 0;
|
|
16658
|
+
const xs = Math.floor(box.x0 * width);
|
|
16659
|
+
const xe = Math.min(width - 1, Math.ceil(box.x1 * width));
|
|
16660
|
+
for (let y = Math.floor(box.y0 * height); y <= Math.min(height - 1, Math.ceil(box.y1 * height)); y++) {
|
|
16661
|
+
for (let x = xs; x <= xe; x++) {
|
|
16662
|
+
const i = y * width + x;
|
|
16663
|
+
if ((ad[i * 4 + 3] ?? 0) === 0) continue;
|
|
16664
|
+
for (let c = 0; c < 3; c++) sum += Math.abs((ad[i * 4 + c] ?? 0) - (paint[i * 3 + c] ?? 0));
|
|
16665
|
+
n++;
|
|
16666
|
+
}
|
|
16667
|
+
}
|
|
16668
|
+
return n > 0 ? sum / n : null;
|
|
16669
|
+
}
|
|
16591
16670
|
|
|
16592
16671
|
// src/engine/nodes/local/staticPlace.ts
|
|
16593
16672
|
var Usage = z25.object({ model: z25.string(), inputTokens: z25.number(), outputTokens: z25.number(), costUsd: z25.number() }).strict();
|
|
16594
16673
|
var Fit = z25.object({ scale: z25.number(), x: z25.number(), y: z25.number() }).strict();
|
|
16674
|
+
var Unit = z25.number().min(0).max(1);
|
|
16675
|
+
var Span = z25.object({ x0: Unit, y0: Unit, x1: Unit, y1: Unit }).strict();
|
|
16595
16676
|
var RenderRatio = z25.string().regex(/^\d+(?:\.\d+)?:\d+(?:\.\d+)?$/);
|
|
16596
16677
|
var Copy = z25.array(z25.object({ text: z25.string().min(1), role: z25.string().optional() }).strict());
|
|
16597
16678
|
var staticPlaceNode = defineNode({
|
|
16598
16679
|
id: "static_place",
|
|
16599
|
-
version: "1.
|
|
16680
|
+
version: "1.1.0",
|
|
16600
16681
|
category: "image",
|
|
16601
16682
|
location: "local",
|
|
16602
|
-
summary: "Place a finished static ad's words and logo where no interface covers them and nothing runs off the frame, for an image model to paint the margins: reads where every line and the logo sit, shrinks the ad (to 60% at most) and sets it inside the size's safe area. `placed` keeps the margins clear (for `static_extend_merge`); `shown` is the same inside the model's render shape with grey margins, to paint. `fit` is null, and both are the ad unchanged, when nothing sits outside.",
|
|
16683
|
+
summary: "Place a finished static ad's words and logo where no interface covers them and nothing runs off the frame, for an image model to paint the margins: reads where every line and the logo sit, shrinks the ad (to 60% at most) and sets it inside the size's safe area. `placed` keeps the margins clear (for `static_extend_merge`); `shown` is the same inside the model's render shape with grey margins, to paint; `keep` is where every line, the logo and the buttons read sit in `placed`, all the merge lays back of the ad's own pixels. `fit` is null, and both are the ad unchanged, when nothing sits outside.",
|
|
16603
16684
|
when_to_use: "Added by a static ad's autofix round (place-and-extend) before an `image_generate` that paints the margins and a `static_extend_merge`; never scaffolded. One box-reader call (about $0.005).",
|
|
16604
16685
|
inputs: z25.object({ image: ImageRef }).strict(),
|
|
16605
16686
|
params: z25.object({
|
|
@@ -16612,6 +16693,8 @@ var staticPlaceNode = defineNode({
|
|
|
16612
16693
|
placed: ImageRef,
|
|
16613
16694
|
shown: ImageRef,
|
|
16614
16695
|
fit: Fit.nullable(),
|
|
16696
|
+
/** Where the words, logo and buttons read sit in `placed`: all the merge lays back of the ad's own pixels. */
|
|
16697
|
+
keep: z25.array(Span).optional(),
|
|
16615
16698
|
reason: z25.enum(["not_read", "inside_band", "too_far"]).optional(),
|
|
16616
16699
|
usage: z25.array(Usage).optional()
|
|
16617
16700
|
}).strict(),
|
|
@@ -16654,18 +16737,28 @@ var staticPlaceNode = defineNode({
|
|
|
16654
16737
|
placed: placedRef,
|
|
16655
16738
|
shown: shownRef,
|
|
16656
16739
|
fit,
|
|
16740
|
+
keep: keptBoxes([...reading.boxes, ...boxes.filter((b) => !reading.boxes.includes(b))], fit),
|
|
16657
16741
|
...spend
|
|
16658
16742
|
};
|
|
16659
16743
|
}
|
|
16660
16744
|
});
|
|
16745
|
+
function keptBoxes(boxes, fit) {
|
|
16746
|
+
const at2 = (offset2, v) => Math.min(1, Math.max(0, offset2 + fit.scale * v));
|
|
16747
|
+
return boxes.map((b) => ({ x0: at2(fit.x, b.x0), y0: at2(fit.y, b.y0), x1: at2(fit.x, b.x1), y1: at2(fit.y, b.y1) }));
|
|
16748
|
+
}
|
|
16661
16749
|
var staticExtendMergeNode = defineNode({
|
|
16662
16750
|
id: "static_extend_merge",
|
|
16663
|
-
version: "1.
|
|
16751
|
+
version: "1.2.0",
|
|
16664
16752
|
category: "image",
|
|
16665
16753
|
location: "local",
|
|
16666
|
-
summary: "Have the image model paint a placed static ad's margins (`static_place`'s `shown`, with `paint`, an `image_generate`'s params) and lay the ad
|
|
16754
|
+
summary: "Have the image model paint a placed static ad's margins (`static_place`'s `shown`, with `paint`, an `image_generate`'s params) and lay the ad's own pixels back over the painting only in `keep` (`static_place`'s boxes of the words, logo and buttons, padded and feathered): the painting is the rest of the frame, one picture with no edge where the shrunk ad was. Without `keep`, or when the painting moved the words, the whole ad (`placed`) is laid back, the painting only in the margins, blended at their seam. Same pixel size as the ad. An ad that was never placed (`shown` is `placed`) passes on as it is, and nothing is painted or billed. A painting already made can be wired as `image` instead of `paint`.",
|
|
16667
16755
|
when_to_use: "Added by a static ad's autofix round after a `static_place`; never scaffolded. One picture at the painter's price, only for a size that was placed.",
|
|
16668
|
-
inputs: z25.object({
|
|
16756
|
+
inputs: z25.object({
|
|
16757
|
+
placed: ImageRef,
|
|
16758
|
+
shown: ImageRef.optional(),
|
|
16759
|
+
image: ImageRef.optional(),
|
|
16760
|
+
keep: z25.array(Span).optional()
|
|
16761
|
+
}).strict(),
|
|
16669
16762
|
params: z25.object({ render_ratio: RenderRatio, paint: z25.record(z25.string(), z25.unknown()).optional() }).strict(),
|
|
16670
16763
|
outputs: z25.object({ image: ImageRef }).strict(),
|
|
16671
16764
|
outputKinds: { image: "image" },
|
|
@@ -16675,7 +16768,7 @@ var staticExtendMergeNode = defineNode({
|
|
|
16675
16768
|
const painted = painting?.path ?? painting?.url;
|
|
16676
16769
|
const placed = inputs.placed.path ?? inputs.placed.url;
|
|
16677
16770
|
if (!painted || !placed) return { image: inputs.placed };
|
|
16678
|
-
const merged = await mergeExtended(await bytesOf(painted), await bytesOf(placed), params.render_ratio);
|
|
16771
|
+
const merged = await mergeExtended(await bytesOf(painted), await bytesOf(placed), params.render_ratio, inputs.keep);
|
|
16679
16772
|
const image = await ctx.assets.ingestBytes({ bytes: merged, kind: "image", mime: "image/png" });
|
|
16680
16773
|
return { image };
|
|
16681
16774
|
}
|
|
@@ -18697,10 +18790,12 @@ export {
|
|
|
18697
18790
|
STATIC_SAFE_FIT_ROLE,
|
|
18698
18791
|
STATIC_PLACE_ROLE,
|
|
18699
18792
|
LOGO_COMPOSITE_ROLE,
|
|
18793
|
+
SERIES_LEAD_ROLE,
|
|
18700
18794
|
STATIC_FIX_ROLE,
|
|
18701
18795
|
LOGO_COMPOSITE_DEFAULT,
|
|
18702
18796
|
nodesWithRole,
|
|
18703
18797
|
rolesById,
|
|
18798
|
+
relaidPictureRole,
|
|
18704
18799
|
formatSlug,
|
|
18705
18800
|
staticMasterFormat,
|
|
18706
18801
|
staticRenderNodes,
|
|
@@ -18758,4 +18853,4 @@ export {
|
|
|
18758
18853
|
defaultRegistry,
|
|
18759
18854
|
createEngineFromEnv
|
|
18760
18855
|
};
|
|
18761
|
-
//# sourceMappingURL=chunk-
|
|
18856
|
+
//# sourceMappingURL=chunk-7NKAKF3K.js.map
|