@koda-sl/baker-cli 0.434.0-dev.42b55fa66 → 0.434.0-dev.4dbd56be6
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 +79 -22
- package/dist/{chunk-MMTPMFFH.js → chunk-7NKAKF3K.js} +438 -149
- package/dist/chunk-7NKAKF3K.js.map +1 -0
- package/dist/{chunk-Q6F5UNOU.js → chunk-7THIX4WW.js} +8 -5
- package/dist/{chunk-Q6F5UNOU.js.map → chunk-7THIX4WW.js.map} +1 -1
- package/dist/{chunk-GCTR4MDE.js → chunk-EURJVJQY.js} +2 -2
- package/dist/{chunk-FQOKJYKN.js → chunk-F7LXMR73.js} +17 -4
- package/dist/chunk-F7LXMR73.js.map +1 -0
- package/dist/{chunk-RFZYRG2Q.js → chunk-QLNGHVRT.js} +11 -3
- package/dist/chunk-QLNGHVRT.js.map +1 -0
- package/dist/{chunk-PHLFNYGQ.js → chunk-VNROEZTR.js} +4 -4
- package/dist/cli.js +16765 -12592
- package/dist/cli.js.map +1 -1
- package/dist/{client-GS7T7ETG.js → client-TCDMWAL3.js} +4 -4
- package/dist/engine/index.d.ts +13 -6
- package/dist/engine/index.js +3 -3
- package/dist/{env-SL22S6CM.js → env-ZPCFZJN5.js} +6 -2
- package/dist/output-CW4JBWIM.js +22 -0
- package/dist/{shared-XR54KFMH.js → shared-6I2TP4YQ.js} +6 -6
- package/package.json +1 -1
- package/dist/chunk-FQOKJYKN.js.map +0 -1
- package/dist/chunk-MMTPMFFH.js.map +0 -1
- package/dist/chunk-RFZYRG2Q.js.map +0 -1
- package/dist/output-OFDKEPPJ.js +0 -18
- /package/dist/{chunk-GCTR4MDE.js.map → chunk-EURJVJQY.js.map} +0 -0
- /package/dist/{chunk-PHLFNYGQ.js.map → chunk-VNROEZTR.js.map} +0 -0
- /package/dist/{client-GS7T7ETG.js.map → client-TCDMWAL3.js.map} +0 -0
- /package/dist/{env-SL22S6CM.js.map → env-ZPCFZJN5.js.map} +0 -0
- /package/dist/{output-OFDKEPPJ.js.map → output-CW4JBWIM.js.map} +0 -0
- /package/dist/{shared-XR54KFMH.js.map → shared-6I2TP4YQ.js.map} +0 -0
package/README.md
CHANGED
|
@@ -46,6 +46,7 @@ export BAKER_CHAT_ID="<chat-id>"
|
|
|
46
46
|
- `BAKER_GSC_SITE_URL` — default GSC site URL. Used when `--site-url` is not passed. If neither is set and exactly one site is verified, it is auto-selected.
|
|
47
47
|
- `BAKER_X_ADS_ACCOUNT_ID` — default X Ads account ID (base36). Used when `--account-id` is not passed. If neither is set and exactly one X Ads account is connected, it is auto-selected.
|
|
48
48
|
- `BAKER_CHAT_ID` — chat context for staged Work Action commands and staged Scheduled agent create/update/delete/unstage. `scheduled-agents run` does not require it. When set, it is also sent as an `x-baker-chat-id` header on every request, so library additions that apply immediately (images, videos, followed brands) are listed in that chat's changes as already applied.
|
|
49
|
+
- `BAKER_THREAD_ID` — set per turn by the sandbox. Sent as an `x-baker-thread-id` header alongside the chat header, so the tools a command reports or changes are those of whoever sent this turn, even while someone else writes in another thread of the same chat.
|
|
49
50
|
|
|
50
51
|
## Debug logging
|
|
51
52
|
|
|
@@ -1116,10 +1117,24 @@ baker ads linkedin analytics --level campaign --campaign-id 1234 \
|
|
|
1116
1117
|
# "Top companies seeing this campaign" (ABM feedback loop) — org names auto-resolved
|
|
1117
1118
|
baker ads linkedin top-companies --campaign-id 1234 --last-days 30
|
|
1118
1119
|
|
|
1120
|
+
# "Which companies are our ads reaching?" — one row per company over the whole period,
|
|
1121
|
+
# most engaged first ({org, name, impressions, clicks, engagements, costLocal}).
|
|
1122
|
+
# Defaults: last 12 calendar months, the only selected ad account, top 100 (--full for all).
|
|
1123
|
+
baker ads linkedin companies --account-id 1234
|
|
1124
|
+
baker ads linkedin companies --page 2 # companies 101-200, most engaged first
|
|
1125
|
+
baker ads linkedin companies --by month --min-impressions 50 # months[] per company
|
|
1126
|
+
baker ads linkedin companies --campaign-id 1,2 --by campaign # campaigns[] per company
|
|
1127
|
+
baker ads linkedin companies --with-domains --stats --output csv # + domain/domainSource; read-quality stats
|
|
1128
|
+
|
|
1119
1129
|
# "Who are these org URNs?" — resolve org URNs to company names (probe leakage)
|
|
1120
1130
|
baker ads linkedin resolve --urns urn:li:organization:17719,urn:li:organization:19022
|
|
1121
1131
|
baker ads linkedin resolve --ids 17719,19022 --output csv
|
|
1122
1132
|
|
|
1133
|
+
# "What are these companies' website domains?" — for matching against CRM companies.
|
|
1134
|
+
# Per org: vanityName (LinkedIn page), website, domain, domainSource (website | subdomain | none);
|
|
1135
|
+
# ids LinkedIn doesn't know land in `errors`. Shortlinks are never followed.
|
|
1136
|
+
baker ads linkedin resolve --ids 1035,1337 --with-domains
|
|
1137
|
+
|
|
1123
1138
|
# "Who is in our audience by industry/seniority/function/title — all at once?"
|
|
1124
1139
|
baker ads linkedin demographics --campaign-id 1234 --last-days 30
|
|
1125
1140
|
|
|
@@ -1188,6 +1203,7 @@ creatives # default ACTIVE intended-status
|
|
|
1188
1203
|
|
|
1189
1204
|
analytics # see "Smart defaults" above (3-axis: level × intent × pivot)
|
|
1190
1205
|
demographics # sweep all firmographic pivots in one call
|
|
1206
|
+
companies # which companies the ads reached, one row per company (--by month|campaign, --with-domains)
|
|
1191
1207
|
top-companies # pivot=MEMBER_COMPANY shortcut (ABM)
|
|
1192
1208
|
conversation # pivot=CONVERSATION_NODE_BUTTON shortcut (Sponsored Messaging)
|
|
1193
1209
|
|
|
@@ -1207,6 +1223,7 @@ audit --format md # deliverable-ready markdown table
|
|
|
1207
1223
|
campaign-groups create|update|pause|resume|duplicate # staged writes (see below)
|
|
1208
1224
|
campaigns create|update|pause|resume|archive|duplicate
|
|
1209
1225
|
campaigns url-params <ad-set-id> # UTMs for every ad in the ad set
|
|
1226
|
+
campaigns exclude-companies <ad-set-id> --org <ids> # stop an ad set reaching these companies
|
|
1210
1227
|
account url-params # UTMs for every ad in the whole account
|
|
1211
1228
|
url-params [--ad-sets] # read what is tagged now, and what lands twice
|
|
1212
1229
|
creatives create|update|pause|resume|duplicate
|
|
@@ -1247,9 +1264,16 @@ baker ads linkedin account url-params --param "utm_source=linkedin&utm_medium=pa
|
|
|
1247
1264
|
baker ads linkedin campaigns update 123456 --audience-expansion off --lan off
|
|
1248
1265
|
baker ads linkedin campaigns update 123456 --bid 7.50 --daily-budget 100 --currency USD
|
|
1249
1266
|
|
|
1250
|
-
# Matched audiences (CSV with header; email columns SHA-256 hashed locally)
|
|
1267
|
+
# Matched audiences (CSV with header; email columns SHA-256 hashed locally).
|
|
1268
|
+
# Company lists take companyName,domain,organizationUrn — organizationUrn matches the company page exactly
|
|
1251
1269
|
baker ads linkedin audiences create --name "ABM Tier-1" --type company-list --list-file accounts.csv
|
|
1252
1270
|
|
|
1271
|
+
# Stop an ad set reaching customers / open deals — add-only; at publish Baker re-reads the ad set
|
|
1272
|
+
# and merges into its targeting as it stands then. --org takes company ids, ≤200 per call;
|
|
1273
|
+
# a longer list goes into a company-list audience passed as --segment
|
|
1274
|
+
baker ads linkedin campaigns exclude-companies 123456 --org 1035,1441
|
|
1275
|
+
baker ads linkedin campaigns exclude-companies 123456 --segment urn:li:dmpSegment:42
|
|
1276
|
+
|
|
1253
1277
|
# Conversions with playbook-default windows
|
|
1254
1278
|
baker ads linkedin conversions create --name "Demo booked" --type LEAD --method INSIGHT_TAG \
|
|
1255
1279
|
--post-click-window 30 --view-window 7
|
|
@@ -1311,7 +1335,7 @@ Each finding: `{id, area, check, status, severity, evidence, fix: {explanation,
|
|
|
1311
1335
|
- OAuth tokens auto-refresh server-side. On 401, reconnect LinkedIn from dashboard → integrations.
|
|
1312
1336
|
- Account scoping: backend rejects calls against an account not in the picker selection.
|
|
1313
1337
|
- Pinned API version: `Linkedin-Version: 202606` (declared in `oauth/constants.ts`). Bump as a coordinated change — LinkedIn deprecates monthly with a ~12-month support window. The floor is `TRACKING_PARAM_DYNAMIC_MIN_API_VERSION` (`CREATIVE_NAME` needs 202606), asserted in `ads/linkedin/utils.test.ts`.
|
|
1314
|
-
- Cache TTLs: accounts/account-detail 1h, listings 30m, analytics 15m–6h (depends on date range), facets 7d, urn-resolve 1h, bid-pricing/forecast 6h, audience-size 1h.
|
|
1338
|
+
- Cache TTLs: accounts/account-detail 1h, listings 30m, analytics 15m–6h (depends on date range), facets 7d, urn-resolve 1h, org domains 7d, bid-pricing/forecast 6h, audience-size 1h.
|
|
1315
1339
|
- `--no-cache` on any command skips both cache layers (the CLI's own and the server's).
|
|
1316
1340
|
|
|
1317
1341
|
#### LinkedIn-specific gotchas
|
|
@@ -1440,7 +1464,7 @@ Shared flags: `--days <n>` (default 30) or `--start-date` / `--end-date` (`YYYY-
|
|
|
1440
1464
|
|
|
1441
1465
|
**`tracking` answers "can this account be reported on at all", and it reads the ad account to do it.** `analytics ads` can only describe parameters that have already arrived, which leaves two blind spots that mislead in the expensive direction: a platform with no traffic in the window looks exactly like a platform whose ads carry nothing, and a dimension arriving under a name nobody has mapped looks exactly like one nothing carries. `baker analytics tracking` reads the mapping, the traffic **and** the client's own Google tracking templates / Meta URL parameters, so it can answer for a platform that has not had a click yet. Every dimension comes back in one of four states, and they are four different jobs: `covered` is done; `unmapped` means the URLs already carry it under a name Baker does not read, so run the `mapCommand` in the response — free, and it repairs the traffic already collected; `missing` means nothing carries it; `unknown` means no visit arrived *and* the account could not be read, which is not a finding and must never be reported as untagged.
|
|
1442
1466
|
|
|
1443
|
-
**`fix` says which level to put the missing pairs at, and who can write it.** Not a field name — the levels that platform accepts tagging at, widest first (the widest is the one to prefer: it covers campaigns nobody has created yet), which of them Baker can stage and which the user has to do by hand, and how each field treats a value already in it. That last part is the one people skip and it differs per platform: a Google **campaign** final URL suffix *replaces* the account one rather than merging, LinkedIn's `url-params` call *sets the ad set's whole parameter set* and Baker cannot read back what is in it, and a Meta creative is immutable so a live ad cannot be retagged through Baker at all. `suffix` is only ever the **missing** pairs, so the rule everywhere is the same: read what is there, work out what is missing, write the union. Where `fix` says Baker has no write surface — Microsoft, TikTok, Reddit, Pinterest, Snapchat — that is a request to hand the user with the exact string and the exact screen, never a reason to stop on the platforms it can write.
|
|
1467
|
+
**`fix` says which level to put the missing pairs at, and who can write it.** Not a field name — the levels that platform accepts tagging at, widest first (the widest is the one to prefer: it covers campaigns nobody has created yet), which of them Baker can stage and which the user has to do by hand, and how each field treats a value already in it. That last part is the one people skip and it differs per platform: a Google **campaign** final URL suffix *replaces* the account one rather than merging, LinkedIn's `url-params` call *sets the ad set's whole parameter set* and Baker cannot read back what is in it, and a Meta creative is immutable so a live ad cannot be retagged through Baker at all. `suffix` is only ever the **missing** pairs, so the rule everywhere is the same: read what is there, work out what is missing, write the union. Where `fix` says Baker has no write surface — Microsoft, TikTok, Reddit, Pinterest, Snapchat — that is a request to hand the user with the exact string and the exact screen, never a reason to stop on the platforms it can write. ChatGPT Ads is one Baker can write: its campaign and ad group `--tracking-template` (`baker ads chatgpt campaigns|ad-groups update`), which should carry `campaign_id={campaign_id}&ad_group_id={ad_group_id}&ad_id={ad_id}` — an id that only rides in `utm_campaign` or `utm_content` is reported as that UTM and never as the campaign or the ad, so `--platform chatgpt --role adgroup_id` has nothing to split by.
|
|
1444
1468
|
|
|
1445
1469
|
**The one recommendation about the ad account itself.** `baker analytics ads --platform <p>` warns when that platform's traffic carries no campaign id, ad group id or ad id — the three tiers every "which ad worked" question is grouped by — and returns the exact URL suffix, in the platform's own macro language, that would start sending the missing ones. It is the only mapping gap that cannot be fixed afterwards: naming a parameter Baker already captures corrects the history, because roles are resolved when a report is read, but a parameter the ads never sent has no stored values for a meaning to apply to. The hint stays quiet for a platform with little traffic, and defers to the untagged-account finding rather than repeating it.
|
|
1446
1470
|
|
|
@@ -1808,16 +1832,19 @@ baker gsc sitemaps --site-url "https://example.com/"
|
|
|
1808
1832
|
Ads start from what customers said. Three commands carry the process; the agent writes the ideas and the copy.
|
|
1809
1833
|
|
|
1810
1834
|
```bash
|
|
1811
|
-
baker creatives brief [--adset <id>] [--account-id act_<id>] # one read: viewer, account bottleneck, what works, taste, customer quotes, ideas on file
|
|
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
|
|
1812
1836
|
baker creatives ideas [--full] # check and rank src/creatives/_ideas/*.md; build_next
|
|
1813
|
-
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
|
|
1814
1839
|
baker creatives learn [--days <n>] [--dry-run] # what each idea's ads did, written back onto the idea
|
|
1815
1840
|
```
|
|
1816
1841
|
|
|
1817
|
-
- `brief` returns a verdict, not raw numbers: `account.bottleneck` is `hook`, `after_click`, `none` or `too_little_data`. A part that cannot be read says `unavailable
|
|
1842
|
+
- `brief` returns a verdict, not raw numbers: `account.bottleneck` is `hook`, `after_click`, `none` or `too_little_data`. A part that cannot be read says `unavailable`; the market searches (the followed brands, and the library for what COMPANY.md's first line says the company sells, in a few words) run at once and get 12 seconds, so a slow ad library leaves them out rather than holding the brief. With few customer quotes on file it runs `research customers` itself alongside the reads (about 30 s) and returns what it wrote; `--no-research` skips that. With no ideas on file, its hint lists the values `awareness`, `angle` and `format` take. `market` says what competitors' ads already say (the followed brands, else the library), how sophisticated the market is, and which awareness stages nobody speaks to.
|
|
1818
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.
|
|
1819
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.
|
|
1820
|
-
- `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. 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.
|
|
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.
|
|
1821
1848
|
|
|
1822
1849
|
### Competitive Intelligence (`baker research`)
|
|
1823
1850
|
|
|
@@ -3573,6 +3600,7 @@ baker scheduled-agents create --name "Celebrate the first sale" --description "O
|
|
|
3573
3600
|
baker scheduled-agents update <id-or-temp_agent_id> --enabled false
|
|
3574
3601
|
baker scheduled-agents update <id-or-temp_agent_id> --run-at "2026-09-02T07:00:00Z" --prompt "Use the launch checklist"
|
|
3575
3602
|
baker scheduled-agents update <id-or-temp_agent_id> --trigger experiment.ended
|
|
3603
|
+
baker scheduled-agents update <id-or-temp_agent_id> --slack "#marketing" # each run opens a thread there; also here | me | off
|
|
3576
3604
|
baker scheduled-agents delete <id-or-temp_agent_id>
|
|
3577
3605
|
baker scheduled-agents unstage <id-or-temp_agent_id> # everything this chat staged on it
|
|
3578
3606
|
baker scheduled-agents unstage <id-or-temp_agent_id> --index 2 # one staged change only
|
|
@@ -3950,7 +3978,7 @@ baker setup-links list # active lin
|
|
|
3950
3978
|
|
|
3951
3979
|
---
|
|
3952
3980
|
|
|
3953
|
-
### `baker hubspot forms list | view | submissions` · `baker hubspot workflows list | view` · `baker hubspot pipelines list` · `baker hubspot contacts summary | lookup | properties | fields` · `baker hubspot deals properties | fields` · `baker hubspot meetings list | view`
|
|
3981
|
+
### `baker hubspot forms list | view | submissions` · `baker hubspot workflows list | view` · `baker hubspot pipelines list` · `baker hubspot contacts summary | lookup | properties | fields` · `baker hubspot companies search` · `baker hubspot deals list | properties | fields` · `baker hubspot meetings list | view`
|
|
3954
3982
|
|
|
3955
3983
|
Read-only view of the company's **connected HubSpot account** — the forms and meeting links (calendars) that live there, the leads those forms received, and the workflows, deal pipelines, and contact records a lead runs through afterwards. Fetched from HubSpot live on every call; nothing is cached and nothing is ever written back.
|
|
3956
3984
|
|
|
@@ -3972,6 +4000,8 @@ baker hubspot pipelines list # where a lead lands, and the st
|
|
|
3972
4000
|
baker hubspot contacts summary --days 30 # are recent leads being worked? (counts only)
|
|
3973
4001
|
baker hubspot contacts lookup lead@example.com # was this one lead picked up?
|
|
3974
4002
|
baker hubspot contacts properties # what a HubSpot-read source can watch, with each value
|
|
4003
|
+
baker hubspot companies search --domains acme.com,globex.io --with-deals # CRM accounts by domain, with their deals
|
|
4004
|
+
baker hubspot deals list --pipeline default --modified-since 2026-01-01 # deals: amount, stage, closed won
|
|
3975
4005
|
baker hubspot meetings list # every calendar + whether booking redirects
|
|
3976
4006
|
baker hubspot meetings view <slug> # booking-form fields for one calendar
|
|
3977
4007
|
baker hubspot meetings view <slug> --as-node # the form.external blob for a flow node
|
|
@@ -3982,7 +4012,9 @@ baker hubspot meetings view <slug> --as-node # the form.external blob for a f
|
|
|
3982
4012
|
**`forms submissions`:** positional `<formId>`; `--days <1-365>` (default 30) sets the window; `--full` adds each submission's field values. The default response is counts and dates only — `total`, `inWindow`, `lastSubmittedAt`, and per-day counts — because submissions carry the lead's own contact details. `truncated: true` means the page budget ran out before the window was covered, so `inWindow` is a floor rather than an exact count.
|
|
3983
4013
|
**`workflows list` flags:** `--search <text>`, `--enabled-only`. Disabled workflows are listed too — a form wired to one that is off looks connected and does nothing.
|
|
3984
4014
|
**`workflows view`:** positional `<workflowId>` (the id from `workflows list`, and the one in HubSpot's own URL — the command translates it to HubSpot's separate internal flow id for you). Returns the enrolment criteria and whether it re-enrols (`null` = HubSpot does not report it for that criteria type, so it is unknown rather than off), plus every step with the property it writes. On a `LIST_BRANCH` step, branches come back in **evaluation order** (`order: 1` wins over `order: 2`) — a broad branch above a narrower one silently swallows the records the narrow one was written for, and HubSpot reports nothing. A `STATIC_BRANCH` matches on a value rather than top down, so its `order` is display only. `steps` is **not** in execution order; follow `nextActionId`. `--full` adds the raw payload, the only place the branch filter criteria (which property is compared to which value) can be read.
|
|
3985
|
-
**`pipelines list`:** no flags; stages come back in the order they run in HubSpot.
|
|
4015
|
+
**`pipelines list`:** no flags; stages come back in the order they run in HubSpot, each with `isClosed` and `isWon` (closed with probability 1).
|
|
4016
|
+
**`companies search` flags:** `--domains <a.com,b.com>` (up to 100; a URL or `www.` is reduced to the bare domain), `--names <"Acme, Inc.;Globex">` (semicolon-separated), `--with-deals`, `--limit <1-500>` (default 100), `--output json|csv|jsonl|md`. Rows: `{ id, name, domain, linkedinPage, deals? }`; `notFound` lists the searched domains no CRM company carries; `truncated: true` means more matched than `--limit`.
|
|
4017
|
+
**`deals list` flags:** `--pipeline <id,id>`, `--modified-since <YYYY-MM-DD>`, `--limit <1-1000>` (default 200), `--after <cursor>`, `--output json|csv|jsonl|md`. Rows, oldest change first: `{ id, name, amount, currency, pipelineId, stageId, stageLabel, isClosed, isWon, createdAt, closedAt, modifiedAt, companyIds }` — `amount: null` is none entered. `next` is the `--after` for the following page; `restartFrom` appears when HubSpot's 10,000-result search ceiling is reached (read on with `--modified-since <restartFrom>`); `companiesReadable: false` means the connection cannot read companies, so no deal carries `companyIds`.
|
|
3986
4018
|
**`contacts summary` flags:** `--days <1-365>` (default 30) over contact creation date. Returns counts only — total contacts, how many have an owner, and the lifecycle-stage split — never a name or an email. `truncated: true` means the page budget ran out, so the counts are floors.
|
|
3987
4019
|
**`contacts lookup`:** positional `<email>`; returns that contact's lifecycle stage, owner, the account (company) it was filed under, and associated deals with their stage. `dealsReadable: false` / `companyReadable: false` mean the connection does not cover deals or accounts, so an empty `deals` or a null `company` is unknown rather than absent. `found: false` means no contact with that email — the lead never reached the CRM, which is a finding rather than an error.
|
|
3988
4020
|
**`contacts properties`:** no flags; returns every contact property with a closed list of values — `{ name, label, groupName, options: [{ value, label }] }` — the ones a HubSpot-read source can watch. Names are HubSpot's internal ones; hand them to `baker analytics sources --create-hubspot --property <name> --value <value>` as printed. No personal data.
|
|
@@ -4014,6 +4046,31 @@ No write surface against HubSpot itself — a form's fields, redirect, or consen
|
|
|
4014
4046
|
|
|
4015
4047
|
---
|
|
4016
4048
|
|
|
4049
|
+
### `baker accounts influenced | engagement | warm | company | campaigns | crm`
|
|
4050
|
+
|
|
4051
|
+
Account insights: which companies the LinkedIn ads are warming, and which CRM deals they influenced. Reads Baker's nightly copy of LinkedIn and the CRM, so it is fast and covers up to 24 months; nothing is changed anywhere. Every company has it once a LinkedIn ad account is chosen; before the first nightly read the reports are empty — read live with `baker ads linkedin companies`. `--start`/`--end` must be real calendar days within 24 calendar months; a bad flag or an unsupported `--output` exits 1 with `INVALID_ARGS`.
|
|
4052
|
+
|
|
4053
|
+
```bash
|
|
4054
|
+
baker accounts influenced # influenced pipeline, last 12 calendar months
|
|
4055
|
+
baker accounts influenced --basis revenue --start 2026-01-01 --end 2026-06-30
|
|
4056
|
+
baker accounts influenced --full # every deal in the period, influenced or not
|
|
4057
|
+
baker accounts engagement --last-months 3 --limit 50 # companies reached, New/Rising, CRM match and deals
|
|
4058
|
+
baker accounts engagement --last-months 3 --limit 50 --page 2 # the next 50 (pageInfo.total counts them all)
|
|
4059
|
+
baker accounts engagement --campaign-id 123456 --output csv
|
|
4060
|
+
baker accounts warm --output csv > warm.csv # companyName,domain,organizationUrn
|
|
4061
|
+
baker accounts company acme.com # or a LinkedIn company id / urn:li:organization:N
|
|
4062
|
+
baker accounts campaigns --last-months 6 --output md # per campaign: reached, engaged, influenced pipeline (--basis revenue)
|
|
4063
|
+
baker accounts influenced --model awareness --threshold 10 --output csv # another rule, this call only
|
|
4064
|
+
baker accounts warm --min 0 # every New/Rising company, below the threshold too
|
|
4065
|
+
baker accounts crm status # deals from HubSpot or sent by the client; what arrived, linked Google Sheets
|
|
4066
|
+
baker accounts crm check --file deals.csv # judge a deals or company CSV offline, nothing sent
|
|
4067
|
+
baker accounts crm contract # the send-your-CRM contract, as Markdown for a developer
|
|
4068
|
+
```
|
|
4069
|
+
|
|
4070
|
+
A deal is **influenced** when its company reached the company's influence rule — by default 3+ LinkedIn ad engagements (Awareness: 7+ impressions; Intent: 3+ clicks) — in the 12 months up to and including the month the deal was created (`--basis pipeline`, deals not won) or closed won (`--basis revenue`). It is a labelled rule, not credit: campaign rows overlap, and money is reported per currency, never summed across currencies. Deals whose company match is only a guess come back under `lowConfidence` and stay out of `summary` until the match is confirmed in Measure → Accounts. Without the deals set up there — HubSpot's pipelines and won/lost stages, or Another CRM — `influenced` and `campaigns` exit 1 with `ACCOUNTS_NOT_SET_UP`. A CRM Baker cannot read sends its companies and deals to `POST /v1/accounts/crm` (a `crm_ingest` key, `X-Baker-Mode: test` to rehearse, up to 500 records per request, records naming a person refused), is uploaded as a CSV in Measure → Accounts, or lives in a Google Sheet linked there that Baker reads every night (a row deleted from the sheet leaves Baker). A file is a deals export or a company list — a company needs only its website or LinkedIn page; `crm check` runs the same rules offline and `crm contract` prints the whole contract. The CLI never sends deals or links a sheet. `--model engagement|awareness|intent` and `--threshold N` on `influenced`, `campaigns` and `warm` read with another rule for that call only; the saved setting is unchanged. A **warm** account is New or Rising, reached the company's influence threshold this period on the rule's metric (`--min N` overrides, `--min 0` drops it), and has no open deal, no won deal and no deal lost in the last 6 months; `engagement` and `warm` rows carry `dealStatus` (`won` > `open` > `lost` > `none`), `engagement --output csv` adds `lastMonth` and `previousImpressions/Clicks/Engagements`, and `campaigns` / `company` carry each campaign's `campaignName` (null when LinkedIn could not name it). `company` returns `months` (the company's totals), `byCampaign` (the same numbers per campaign — overlapping, never add them) and its `deals` with `touchValue` and `influenced`. `warm --output csv` is the file `baker ads linkedin audiences create --type company-list --list-file` takes.
|
|
4071
|
+
|
|
4072
|
+
---
|
|
4073
|
+
|
|
4017
4074
|
### `baker chats list | view | transcript | diff`
|
|
4018
4075
|
|
|
4019
4076
|
Read-only inspection of **other Sessions (chats) on the same account** — so an agent can reuse prior work ("like we did in that other chat, but for X"). Same-account only; never crosses a company boundary and has no write surface.
|
|
@@ -4627,7 +4684,7 @@ Open record keyed by the composition's `meta.json` inputs.
|
|
|
4627
4684
|
|
|
4628
4685
|
##### `static_prefilter`
|
|
4629
4686
|
|
|
4630
|
-
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.
|
|
4687
|
+
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), `image` (the chosen picture: when a variant rendered at another shape, a 3:4 for a 4:5, would lose a word or the button to a cut about its centre, it comes cut to the master's frame where every word stays whole, else as rendered) and `usage` (the reader's spend). Scaffolded by `scaffold-static-ad` between the variants and the judge.
|
|
4631
4688
|
|
|
4632
4689
|
##### `static_safe_fit`
|
|
4633
4690
|
|
|
@@ -5832,8 +5889,8 @@ offered, or, for a viewer who does not know the brand, when the words say they a
|
|
|
5832
5889
|
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
|
|
5833
5890
|
`_definition.md`, else the one its `targetAdSet` reaches (as `baker ads meta adsets --audience` reads it), else
|
|
5834
5891
|
the persona and `awareness` from the Knowledge, else a cold stranger. Not understood → exit 2 with
|
|
5835
|
-
`viewer_check_failed` and a `fix`
|
|
5836
|
-
`baker canvas run` makes the same check on a static ad before it spends (skipped for `--fix`, an autofix round, `--use-variant`, a re-layout and resumed runs, never for an edit of the words;
|
|
5892
|
+
`viewer_check_failed` and a `fix` that starts with the one rewrite answering a missed offer (`Do this: make the biggest words say "<message>"`) and names what the viewer missed. After two failures of one ad in a chat turn the check no longer holds it back: the command answers `ok: true` with `understood: false` and what was missed, and `baker canvas run` renders it, printing the miss. The casting check is asked alongside the viewer. Answers are cached per copy, scene and viewer.
|
|
5893
|
+
Before either, a static ad whose scene quotes words, or names a price or a share, that the copy (or `scene_text`) does not hold is refused (`scene_words_not_in_copy`): the picture would print them and a correction round would erase them. An unreadable `prompt.json` names each wrong field in a line and shows that field's shape. `baker canvas run` makes the same check on a static ad before it spends (skipped for `--fix`, an autofix round, `--use-variant`, a re-layout and resumed runs, never for an edit of the words;
|
|
5837
5894
|
`--skip-viewer-check` renders without it; a model outage lets the render through with a warning). `--full` adds
|
|
5838
5895
|
the viewer's whole answer and how the viewer was chosen.
|
|
5839
5896
|
|
|
@@ -5864,7 +5921,7 @@ A completed render the review calls ready (`verdict` `ready` or `ready_with_note
|
|
|
5864
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`). |
|
|
5865
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). |
|
|
5866
5923
|
| `--from <run_id>` | the last render | With `--fix` or `--use-variant`: the version to start from. |
|
|
5867
|
-
| `--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. |
|
|
5868
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. |
|
|
5869
5926
|
| `--remote-cache <on\|off>` | `on` (or `BAKER_CANVAS_REMOTE_CACHE`) | Operator only. Company-scoped remote cache + durable asset persistence. |
|
|
5870
5927
|
| `--no-record` | records | Skip posting the durable run-history record (and its live progress). |
|
|
@@ -5879,7 +5936,7 @@ A completed render the review calls ready (`verdict` `ready` or `ready_with_note
|
|
|
5879
5936
|
|
|
5880
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`).
|
|
5881
5938
|
|
|
5882
|
-
**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.
|
|
5883
5940
|
|
|
5884
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.
|
|
5885
5942
|
|
|
@@ -5992,7 +6049,7 @@ The two scaffold passes are billed (the full `video_deconstruct` is the heavy on
|
|
|
5992
6049
|
|
|
5993
6050
|
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:
|
|
5994
6051
|
|
|
5995
|
-
1. **`image_describe`** (`~google/gemini-pro-latest`) — reverse-engineers the image (layout by what fills each region, copy verbatim, typefaces, capture in words, the `mechanism` and the `audience` it is built for) and writes it next to the canvas as **`prompt.json`**, an **ad spec** (`"schema": "baker-static-ad/3"`; `/2` still reads) you rewrite into the ad you want: `viewer` (who sees it and what they already know), `message` (the one thing it tells them, never printed), `copy` (each line with `lettering`: `in_scene`, written on something in the picture (`on`, `look`), or `designed`, set in the brand's face), `scene` (`description`, `look`: photo | designed | illustration, `capture` in words, `mood`, `layout`), `brand`, `colors` (each tied to an object, from the brand palette), `references` (one picture each, labelled by role: `PERSON_1`, `ANIMAL_1`, `PRODUCT_1`, `BADGE_1`, `PROP_1`), `logo` (and `logo_position`), `third_party_brands` (other brands the offer is about, a car brand on a car loan: `name`, `what`, and `logo` naming the `BADGE_n` reference of its logo; their logos may appear), an optional `direction`, the `mechanism` to rebuild for our brand, and in `/3`: `viewer.market` (`country`, `language`, `currency`), `brand_presence` (`standard` | `native`: a native disguise carries the logo as its sender and no big button), `recognition` (the kind of product a stranger must take it for at a glance, never the format), `native` (the ad is a screen people know, drawn exactly from HTML at every size by the free `static_native` node instead of painted: `x_post` (a post or a thread, with `reply_to`), `whatsapp`, `imessage`, `trustpilot`, `google_reviews`, `notes`, `slack`; on a 9:16 the app is drawn full screen as a phone shows it: status bar and app header pinned at the top, the reply bar and tab bar (or a keyboard under a chat or note) pinned at the bottom, the posts or messages as large as the clear band holds, and the frame's headline, caption and logo a card in the brand's colours under the app's header; a wall of reviews (`trustpilot`, `google_reviews`) takes up to 16 real reviews, the first two (`FEATURED_REVIEWS`) the ad's copy set whole in the safe area and the rest filling the whole frame behind them as large, sharp cards, turned and cut by its edges, because a wall wins on how many there are; a review longer than 160 characters is shown cut at a word with "…", and that cut text is the copy the review reads back; a chat (`whatsapp`, `imessage`) on a 9:16 never shrinks below about 62% of the phone scale: its earliest messages scroll up under the header, as the app does, the node's optional `scrolled` output lists the copy lines scrolled off that size, and the run review expects those lines nowhere on it; a logo file whose drawing runs off its own edge is set aside for a whole one, and a square mark where a wordmark goes is drawn larger; the run review never flags the app's own interface words (status bar, keyboard) as under Reels; the image model renders only the screen's `photos`, each a plain phone photo of ordinary people of the market; adding the block switches an existing ad on its next run, `--fix` does not apply, an invented brand handle or a rating the Knowledge does not hold (either decimal mark) is refused as `native_screen_invalid`, and a photo of ours goes in as its `image`: a native photo takes no `avatar`, since nothing would cast it), a role on each third-party brand (`partner` | `proof` | `category_signal`), a reference's `image` (wired as a picture of ours) and a person's `avatar` (the company avatar cast), and `concept` (`why_it_wins` in five layers, `load_bearing` elements with `have`/`adapt`/`missing` and `ours`, `verdict` `use`|`adapt`|`reject`, `transfer_note`; seeded from `baker winning-ads fit`). Every render keeps the load-bearing elements; `validate`/`run` refuse a `reject` or a `missing` element with nothing named (`concept_does_not_fit`) and a rating, or a promised price, share, multiple or time, the Knowledge does not hold with that unit (`proof_without_source`); a line citing a review platform gets its real badge wired. **You never write a model prompt**: every render prompt is compiled from this file for the model it runs on (short labelled sections for gpt-image, sentences with reasons for Gemini; each line quoted once; pictures numbered with one role each; colours from the brand; realism as capture cues; the safe area from its one owner; constraints last), on every `validate` and `run`, and shown on its node. A `prompt.json` written before the ad spec (the describe blueprint) is read through an adapter and keeps working. The source's own read (`source`, and the library's `reference`) stays as bookkeeping and is never sent to a model.
|
|
6052
|
+
1. **`image_describe`** (`~google/gemini-pro-latest`) — reverse-engineers the image (layout by what fills each region, copy verbatim, typefaces, capture in words, the `mechanism` and the `audience` it is built for) and writes it next to the canvas as **`prompt.json`**, an **ad spec** (`"schema": "baker-static-ad/3"`; `/2` still reads) you rewrite into the ad you want: `viewer` (who sees it and what they already know), `message` (the one thing it tells them, never printed), `copy` (each line with `lettering`: `in_scene`, written on something in the picture (`on`, `look`), or `designed`, set in the brand's face), `scene` (`description`, `look`: photo | designed | illustration, `capture` in words, `mood`, `layout`), `brand`, `colors` (each tied to an object, from the brand palette), `references` (one picture each, labelled by role: `PERSON_1`, `ANIMAL_1`, `PRODUCT_1`, `BADGE_1`, `PROP_1`), `logo` (and `logo_position`), `third_party_brands` (other brands the offer is about, a car brand on a car loan: `name`, `what`, and `logo` naming the `BADGE_n` reference of its logo; their logos may appear), an optional `direction`, the `mechanism` to rebuild for our brand, and in `/3`: `viewer.market` (`country`, `language`, `currency`), `brand_presence` (`standard` | `native`: a native disguise carries the logo as its sender and no big button), `recognition` (the kind of product a stranger must take it for at a glance, never the format), `native` (the ad is a screen people know, drawn exactly from HTML at every size by the free `static_native` node instead of painted: `x_post` (a post or a thread, with `reply_to`), `whatsapp`, `imessage`, `trustpilot`, `google_reviews`, `notes`, `slack`; on a 9:16 the app is drawn full screen as a phone shows it: status bar and app header pinned at the top, the reply bar and tab bar (or a keyboard under a chat or note) pinned at the bottom, the posts or messages as large as the clear band holds, and the frame's headline, caption and logo a card in the brand's colours under the app's header; a wall of reviews (`trustpilot`, `google_reviews`) takes up to 16 real reviews, the first two (`FEATURED_REVIEWS`) the ad's copy set whole in the safe area and the rest filling the whole frame behind them as large, sharp cards, turned and cut by its edges, because a wall wins on how many there are; a review longer than 160 characters is shown cut at a word with "…", and that cut text is the copy the review reads back; a chat (`whatsapp`, `imessage`) on a 9:16 never shrinks below about 62% of the phone scale: its earliest messages scroll up under the header, as the app does, the node's optional `scrolled` output lists the copy lines scrolled off that size, and the run review expects those lines nowhere on it; a logo file whose drawing runs off its own edge is set aside for a whole one, and a square mark where a wordmark goes is drawn larger; the run review never flags the app's own interface words (status bar, keyboard) as under Reels; the image model renders only the screen's `photos`, each a plain phone photo of ordinary people of the market; adding the block switches an existing ad on its next run, `--fix` does not apply, an invented brand handle or a rating the Knowledge does not hold (either decimal mark) is refused as `native_screen_invalid`, and a photo of ours goes in as its `image`: a native photo takes no `avatar`, since nothing would cast it), a role on each third-party brand (`partner` | `proof` | `category_signal`), a reference's `image` (wired as a picture of ours) and a person's `avatar` (the company avatar cast), and `concept` (`why_it_wins` in five layers, `load_bearing` elements with `have`/`adapt`/`missing` and `ours`, `verdict` `use`|`adapt`|`reject`, `transfer_note`; seeded from `baker winning-ads fit`). Every render keeps the load-bearing elements; `validate`/`run` refuse a `reject` or a `missing` element with nothing named (`concept_does_not_fit`) and a rating, or a promised price, share, multiple or time, the Knowledge does not hold with that unit, a number spelled in words included, or a superlative it does not state in the same words (`proof_without_source`); a line citing a review platform gets its real badge wired. **You never write a model prompt**: every render prompt is compiled from this file for the model it runs on (short labelled sections for gpt-image, sentences with reasons for Gemini; each line quoted once; pictures numbered with one role each; colours from the brand; realism as capture cues; the safe area from its one owner; constraints last), on every `validate` and `run`, and shown on its node. A `prompt.json` written before the ad spec (the describe blueprint) is read through an adapter and keeps working. The source's own read (`source`, and the library's `reference`) stays as bookkeeping and is never sent to a model.
|
|
5996
6053
|
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), which seeds the spec's `brand.typeface`. Background extras are dropped. Each element becomes a `references` entry in the spec, named by its role and naming the canvas slot it fills, so the file says which slot grounds which subject.
|
|
5997
6054
|
|
|
5998
6055
|
It then scaffolds a canvas on which **the image model designs the finished ad**, holding only what `prompt.json` holds; no element is mandatory:
|
|
@@ -6009,7 +6066,7 @@ The canvas is validated before it is written. stdout returns `{ ok, canvas_path,
|
|
|
6009
6066
|
|
|
6010
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.
|
|
6011
6068
|
|
|
6012
|
-
**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; 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.
|
|
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.
|
|
6013
6070
|
|
|
6014
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.
|
|
6015
6072
|
|
|
@@ -6536,7 +6593,7 @@ import {
|
|
|
6536
6593
|
|
|
6537
6594
|
## Creatives
|
|
6538
6595
|
|
|
6539
|
-
|
|
6596
|
+
Save a finished canvas render as a Baker creative: a static image (PNG, JPG, WebP) or a finished video (MP4, MOV, WebM). Choose and save the best finished version without waiting for the user to choose, including in the turn that rendered it. Saving pins the version as the agent's choice without recording user approval or putting an ad live. An image uploads to the Baker image library (tagged `creative`); a video uploads to the Baker video library and is saved once it plays (bounded at 3 minutes). A creative record is created/updated, and the command prints the creative reference JSON the dashboard renders in chat.
|
|
6540
6597
|
|
|
6541
6598
|
```bash
|
|
6542
6599
|
baker creatives publish --title "Spring Offer 4x5" \
|
|
@@ -6554,7 +6611,7 @@ baker creatives publish ./canvas/<run_id>/final.mp4 --title "Spring Offer Reel"
|
|
|
6554
6611
|
| `<file>` | The image or video. For an image it is optional with `--run-id`: the run's own saved output (from its run record, sha-verified) is used instead, so a workspace that never had the run's folder can publish it. |
|
|
6555
6612
|
| `--title <text>` | Required. Human title for the creative. |
|
|
6556
6613
|
| `--slug <slug>` | Creative slug (`src/creatives/<slug>/`): attaches the image or video to that creative's row, marks it `published`. Required for a video. With `--run-id` an image publish also sets `status: published` in the creative's `_definition.md` (`definitionStatus` in the output). |
|
|
6557
|
-
| `--run-id <r_…>` | Pins the
|
|
6614
|
+
| `--run-id <r_…>` | Pins the finished generation from the creative's run history as the saved one. |
|
|
6558
6615
|
| `--output <label\|slot\|n>` | With `--run-id` and no file: which of the run's images to save when it made several (a label such as `4:5`, a slot, or a 1-based number). |
|
|
6559
6616
|
| `--source-reference-url <url>` | Original reference ad URL, recorded on the creative. |
|
|
6560
6617
|
| `--context <text>` | Optional describe context for the uploaded image or video. |
|
|
@@ -6599,7 +6656,7 @@ With `--slug`, `data.variantChoice` (`runId`, `option`, `sha256`) says which var
|
|
|
6599
6656
|
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.
|
|
6600
6657
|
|
|
6601
6658
|
```bash
|
|
6602
|
-
baker creatives feedback record --slug spring-offer --
|
|
6659
|
+
baker creatives feedback record --slug spring-offer --verdict rejected \
|
|
6603
6660
|
--reasons text_mistakes,weak_hook --note "the price is 49 not 59"
|
|
6604
6661
|
baker creatives feedback record --slug spring-offer --run r_01K… --option 2 --verdict approved
|
|
6605
6662
|
```
|
|
@@ -6607,7 +6664,7 @@ baker creatives feedback record --slug spring-offer --run r_01K… --option 2 --
|
|
|
6607
6664
|
| Flag | Effect |
|
|
6608
6665
|
|---|---|
|
|
6609
6666
|
| `--slug <slug>` | Required. The creative (`src/creatives/<slug>/`). |
|
|
6610
|
-
| `--run <r_…>` |
|
|
6667
|
+
| `--run <r_…>` | The version. Left out, the one the creative shows now (its last render in this workspace). |
|
|
6611
6668
|
| `--verdict approved\|rejected` | Required. |
|
|
6612
6669
|
| `--option <o>` | One option of a version that made several: its label, its number (`2`) or its slot. |
|
|
6613
6670
|
| `--reasons <a,b>` | Comma-separated: `text_mistakes`, `logo_or_brand_wrong`, `weak_hook`, `wrong_message`, `looks_fake`, `not_our_style`, `voice_or_sound`, `pacing`, `other`. The two video reasons: `voice_or_sound` (the voiceover, music or mix) and `pacing` (too slow, too fast, the hook lands late). A rejection needs reasons or a note. |
|
|
@@ -6617,10 +6674,10 @@ After a rejection, `hints[]` restates what to change, starting from that version
|
|
|
6617
6674
|
|
|
6618
6675
|
### `baker creatives select`
|
|
6619
6676
|
|
|
6620
|
-
|
|
6677
|
+
Record the user's choice of a creative version, or only some of its options, the way choosing it in Baker does: the creative shows that version as the chosen one, and the choice records the chat sender's approval (`source: "selection"`). Naming options replaces the earlier pick. It does not add the image to the library; that is `baker creatives publish`, which pins the agent's saved version without recording user approval. Use `select` only for a choice the user actually made; autonomous saving uses `publish`.
|
|
6621
6678
|
|
|
6622
6679
|
```bash
|
|
6623
|
-
baker creatives select --slug spring-offer
|
|
6680
|
+
baker creatives select --slug spring-offer # the version it shows now
|
|
6624
6681
|
baker creatives select --slug spring-offer --run r_01K… --options "beach,2"
|
|
6625
6682
|
```
|
|
6626
6683
|
|