@koda-sl/baker-cli 0.270.1-dev.1f1c09c80 → 0.270.2-dev.f69bf4bac
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 +112 -32
- package/dist/{chunk-XAUTT2Q6.js → chunk-ARUM5SYE.js} +2 -2
- package/dist/{chunk-RHEQTHT7.js → chunk-IK3GODKH.js} +3 -3
- package/dist/{chunk-P2PEABMQ.js → chunk-KDTHRRAC.js} +10 -3
- package/dist/chunk-KDTHRRAC.js.map +1 -0
- package/dist/{chunk-SZJWEOW2.js → chunk-LDDTPUYV.js} +22 -9
- package/dist/chunk-LDDTPUYV.js.map +1 -0
- package/dist/cli.js +7563 -5090
- package/dist/cli.js.map +1 -1
- package/dist/{client-KOUPT7HU.js → client-6KQHCXS2.js} +4 -2
- package/dist/engine/index.js +1 -1
- package/dist/{output-ALQGBDPQ.js → output-2BQGB5YF.js} +3 -3
- package/dist/{shared-UKJH3PS7.js → shared-CAO5EVLO.js} +4 -4
- package/package.json +1 -1
- package/dist/chunk-P2PEABMQ.js.map +0 -1
- package/dist/chunk-SZJWEOW2.js.map +0 -1
- /package/dist/{chunk-XAUTT2Q6.js.map → chunk-ARUM5SYE.js.map} +0 -0
- /package/dist/{chunk-RHEQTHT7.js.map → chunk-IK3GODKH.js.map} +0 -0
- /package/dist/{client-KOUPT7HU.js.map → client-6KQHCXS2.js.map} +0 -0
- /package/dist/{output-ALQGBDPQ.js.map → output-2BQGB5YF.js.map} +0 -0
- /package/dist/{shared-UKJH3PS7.js.map → shared-CAO5EVLO.js.map} +0 -0
package/README.md
CHANGED
|
@@ -226,6 +226,11 @@ baker ads google query "SELECT campaign.name, metrics.clicks, metrics.cost_micro
|
|
|
226
226
|
# Use a preset (saves tokens) — returns only actually-serving entities by default
|
|
227
227
|
baker ads google query --preset campaign-performance --customer-id 1234567890
|
|
228
228
|
|
|
229
|
+
# Campaigns Google reports as limited by budget, with each one's bid target next to its
|
|
230
|
+
# actual cost per conversion. Since 17 Aug 2026 a budget-limited campaign delivers toward
|
|
231
|
+
# its target instead of holding its recent actual cost, so a target above actual raises cost.
|
|
232
|
+
baker ads google query --preset budget-limited --customer-id 1234567890
|
|
233
|
+
|
|
229
234
|
# Include paused entities too (default is serving-only)
|
|
230
235
|
baker ads google query --preset ad-copy-performance --customer-id 1234567890 --include-paused
|
|
231
236
|
|
|
@@ -653,17 +658,21 @@ baker tag-manager draft show --chat <chat-id> gtm_temp_ab12
|
|
|
653
658
|
baker actions draft --chat <chat-id>
|
|
654
659
|
```
|
|
655
660
|
|
|
656
|
-
Command groups: `budgets`, `campaigns`, `ad-groups`, `keywords` (add/update/remove), `negative-keywords`, `keyword-lists`, `ads`, `assets` (create/update/attach/detach), `asset-groups` (create/update/attach/detach — Performance Max), `audiences`, `conversions` (create/update/goal), `bidding-strategies`, `labels`, `campaign-criteria` (add/update/remove), and `draft`. Amounts are in major currency units (converted to micros). Money/bids: `--amount`, `--cpc-bid`, `--target-cpa`, `--max-cpc` take major units; `--target-roas` a ratio. `--max-cpc` sets the max CPC bid ceiling for `TARGET_IMPRESSION_SHARE` / `TARGET_SPEND` / `PERCENT_CPC` and must be paired with `--bidding-strategy`. Less-common ops accept a `--file
|
|
661
|
+
Command groups: `budgets`, `campaigns`, `ad-groups`, `keywords` (add/update/remove), `negative-keywords`, `keyword-lists`, `ads`, `assets` (create/update/attach/detach), `asset-groups` (create/update/attach/detach — Performance Max), `audiences`, `conversions` (create/update/goal/upload), `bidding-strategies`, `labels`, `campaign-criteria` (add/update/remove), and `draft`. Amounts are in major currency units (converted to micros). Money/bids: `--amount`, `--cpc-bid`, `--target-cpa`, `--target-cpc`, `--max-cpc` take major units; `--target-roas` a ratio. `--target-cpc` pairs with `--bidding-strategy TARGET_CPC` and is Demand Gen only. `--max-cpc` sets the max CPC bid ceiling for `TARGET_IMPRESSION_SHARE` / `TARGET_SPEND` / `PERCENT_CPC` and must be paired with `--bidding-strategy`. Less-common ops accept a `--file` payload — a path to a JSON file, or the JSON object written inline (flags override its keys). Updates target a resource name or bare id as the positional argument; a target that names an op staged earlier **amends it in place**.
|
|
657
662
|
|
|
658
663
|
**A device criterion can be added, but never removed** — `campaign-criteria add` with a device criterion works, but Google then rejects `campaign-criteria remove` on it forever, including on a criterion you added yourself (and Baker refuses the removal before publish). Adjust it instead with `campaign-criteria update customers/<cid>/campaignCriteria/<campaignId>~<criterionId> --bid-modifier <n>`: `1` for no adjustment, `1.25` to bid 25% more, `0.9` to bid 10% less, `0` to stop serving on that device. Locations, languages and ad schedules remove normally.
|
|
659
664
|
|
|
665
|
+
**Images on a Search campaign** — `assets create` with an image publishes it into the advertiser's Google Ads asset library, but Google refuses the asset link that puts an image extension on a Search campaign (`AD_IMAGE` and every marketing-image field type, at campaign, ad group and customer level alike), so `assets attach` rejects it at stage time and names what to do instead. Staging an image therefore also stages a **Task** — "Add N images to <campaign> in Google Ads" — which applies with the rest of the chat, so the one step Google keeps inside its own interface is tracked rather than mentioned. Attach the image to a Performance Max asset group (`asset-groups attach`) and the Task withdraws itself. The one image a Search campaign does take through a link is the advertiser's logo: `BUSINESS_LOGO`, at campaign or customer level, on a verified account.
|
|
666
|
+
|
|
660
667
|
**Primary vs secondary conversion actions** — `conversions update <id> --primary` makes an action a primary action (automated bidding optimizes toward it); `--no-primary` (or `--primary=false`) demotes it to secondary (reported only) — the spaced form `--primary false` is rejected, since a boolean flag written with a space is set to true and the word dropped. `conversions create` stages a new action as **secondary** unless its `--file` payload sets `"primaryForGoal": true`, so a new action never silently joins the bidding target of a Maximize Conversions or Target CPA campaign. The staged card shows an "Action optimization" row with the before/after role.
|
|
661
668
|
|
|
662
669
|
**Account-level conversion goals** — `conversions goal --customer-id … --category PURCHASE --origin WEBSITE --biddable` sets Google's `CustomerConversionGoal` for one (category, origin) pair; `--no-biddable` (or `--biddable=false`) excludes the category from bidding while it keeps being measured. Neither flag defaults, and the spaced form `--biddable false` is rejected. A conversion drives automated bidding only when both halves are on — the action is primary AND its category's account-level goal is biddable — so this is where "my conversion is Primary but bidding ignores it" is fixed. One staged op per pair.
|
|
663
670
|
|
|
671
|
+
**Uploading conversions Google never received** — `conversions upload --customer-id 1234567890 --conversion-action 987654321 --file missing.csv` files conversions the account has no record of, for a period when the tracking was broken. The file is a CSV, JSON or one-object-per-line export whose header names the columns `orderId`, `conversionDateTime`, `gclid` / `gbraid` / `wbraid`, `email`, `phone`, `value`, `currency`; column names are matched loosely (`Order ID`, `order_id`, `transaction_id` are one column) and any column an upload has no use for is ignored and listed back in the hints. A single conversion can be given with `--order-id --date --gclid --email --value --currency` instead. Each conversion needs an order id, a date **with its timezone** (a bare `2026-08-01T10:00:00` is refused, since it means one instant to the person who typed it and another to everything else), and something to match on — a click id or the customer's email / phone. Reuse each conversion's **original** order id: that is what makes a re-send a correction rather than a second conversion. A value always carries its currency, because Google reads one without in the account's own. Emails and phone numbers are hashed at stage time, so nothing identifying is stored, sent in the clear, or kept afterwards. Staging checks the conversion action exists and is not removed (either refuses), and warns about conversions older than that action's click-through window — Google accepts those and attributes them to nothing. Like every other Google write it applies on publish; more than 200 conversions stage as several uploads that publish independently.
|
|
672
|
+
|
|
664
673
|
**Retiring a conversion action** — `status` accepts `ENABLED` and `REMOVED` only. Google denies `HIDDEN` on every conversion-action write, so to stop bidding toward an action while it keeps measuring use `--no-primary`, and to stop it counting at all use `"status": "REMOVED"`. Conversion actions **Google owns** cannot be changed at all — Smart campaign call/directions actions, the in-ad lead form's submit, YouTube subscriptions and follow-on views, Floodlight and Search Ads 360 imports, store visits, codeless webpage actions. Staging any edit to one, a rename included, is refused up front; there is no manual route either, since the Google Ads interface will not change them.
|
|
665
674
|
|
|
666
|
-
**Reviewing the draft** — `baker ads google draft list` renders everything you've staged as a grouped campaign ▸ ad group ▸ ad tree (with non-blocking completeness advisories), the CLI counterpart to the dashboard's Google Ads tab. Pass `--json` for the raw envelope. Aim for a fully built campaign — 2–4 ad groups, ≥5 keywords each, 2–4 RSAs with 8–12 headlines, ≥4 sitelinks / ≥3 callouts / ≥1 structured snippet, and ≥1 shared negative list; the advisories flag what's still thin.
|
|
675
|
+
**Reviewing the draft** — `baker ads google draft list` renders everything you've staged as a grouped campaign ▸ ad group ▸ ad tree (with non-blocking completeness advisories), the CLI counterpart to the dashboard's Google Ads tab. Pass `--json` for the raw envelope. Once the chat has published it reports the outcome instead — `published · N applied, M failed`, with a `✗` line naming each op that did not land and why. Aim for a fully built campaign — 2–4 ad groups, ≥5 keywords each, 2–4 RSAs with 8–12 headlines, ≥4 sitelinks / ≥3 callouts / ≥1 structured snippet, and ≥1 shared negative list; the advisories flag what's still thin.
|
|
667
676
|
|
|
668
677
|
**Amending a staged op** — `draft amend <ref> (--file patch.json | --patch '<json>')` merges a JSON patch into an already-staged op's payload (objects deep-merge, `null` deletes a key, arrays/scalars replace) and re-validates it in full. Always prefer this over removing and re-staging — it preserves the op's ref (so dependents don't break) and its position in the draft. `draft show <ref>` prints the full staged payload (including warnings/annotations) so you can verify a change looks right before publish.
|
|
669
678
|
|
|
@@ -1063,6 +1072,8 @@ audit --format md # deliverable-ready markdown table
|
|
|
1063
1072
|
campaign-groups create|update|pause|resume|duplicate # staged writes (see below)
|
|
1064
1073
|
campaigns create|update|pause|resume|archive|duplicate
|
|
1065
1074
|
campaigns url-params <ad-set-id> # UTMs for every ad in the ad set
|
|
1075
|
+
account url-params # UTMs for every ad in the whole account
|
|
1076
|
+
url-params [--ad-sets] # read what is tagged now, and what lands twice
|
|
1066
1077
|
creatives create|update|pause|resume|duplicate
|
|
1067
1078
|
audiences create|upload
|
|
1068
1079
|
conversions create|update
|
|
@@ -1090,6 +1101,10 @@ baker ads linkedin campaigns url-params 123456 \
|
|
|
1090
1101
|
--param "utm_source=linkedin&utm_medium=paid-social" \
|
|
1091
1102
|
--dynamic utm_campaign=CAMPAIGN_NAME --dynamic utm_content=CREATIVE_ID
|
|
1092
1103
|
baker ads linkedin campaigns url-params 123456 --clear # remove them all
|
|
1104
|
+
|
|
1105
|
+
# what never varies across the account goes one level up — LinkedIn appends BOTH,
|
|
1106
|
+
# so a key set at the account AND the ad set lands in the URL twice
|
|
1107
|
+
baker ads linkedin account url-params --param "utm_source=linkedin&utm_medium=paid_social"
|
|
1093
1108
|
# a boolean flag written with a space (`--clear false`) is rejected: citty sets it to TRUE and drops
|
|
1094
1109
|
# the word. Attach it — `--clear=false` — or leave the flag off.
|
|
1095
1110
|
|
|
@@ -1217,6 +1232,10 @@ baker analytics tracking # do the ad URLs carry the campaign, a
|
|
|
1217
1232
|
baker analytics tracking --platform meta # the same for one platform, read from their own ad account
|
|
1218
1233
|
baker analytics map --platform google --set kw=keyword # name a parameter, seen or not yet
|
|
1219
1234
|
baker analytics map --platform google --remove kw # take that answer back
|
|
1235
|
+
baker analytics conversions # what counts as a conversion, and what each one produced
|
|
1236
|
+
baker analytics conversions --candidates # everything these pages do, and which of it is counted
|
|
1237
|
+
baker analytics conversions --event page:request_demo --name "Demo requested"
|
|
1238
|
+
baker analytics conversions --remove page:request_demo
|
|
1220
1239
|
baker analytics delivery --page 2 # the next page of a long list
|
|
1221
1240
|
baker analytics submissions --flow contact # every attempt at a Form, delivered or not
|
|
1222
1241
|
```
|
|
@@ -1229,7 +1248,9 @@ Shared flags: `--days <n>` (default 30) or `--start-date` / `--end-date` (`YYYY-
|
|
|
1229
1248
|
- The `sparkline` on `landings` is dropped unless `--full`, and resampled to 30 points when present. It is bucketed by `--granularity` upstream, so ninety days at `hour` is 2,160 numbers per page.
|
|
1230
1249
|
- Past ~25 rows the response carries a hint naming the row count and pointing at `--output md`.
|
|
1231
1250
|
|
|
1232
|
-
**Paging the list reports.** `people`, `submissions` and `delivery` are lists of individual things rather than breakdowns — `delivery` and `submissions` are the ones the CLI exposes, `people` backs the dashboard's
|
|
1251
|
+
**Paging the list reports.** `people`, `submissions` and `delivery` are lists of individual things rather than breakdowns — `delivery` and `submissions` are the ones the CLI exposes, `people` backs the People section of the dashboard's landing Analytics screen over the same wire — and only they take `--page <n>` (1-based) and `--page-size <n>` (up to 200, default 50). The response carries `pageInfo` with `hasMore` — when it is true there **are** more rows, so a total must never be reported from one page. Pass neither flag and a list comes back at the size it always did.
|
|
1252
|
+
|
|
1253
|
+
**A conversion is a company-level named event, not a property of a Form.** `baker analytics conversions` is the only place "what counts" is decided, and an empty `definitions` list means every conversion number in every other report is zero — not because nobody converted, but because nothing is named as an outcome and Baker never guesses which event is the point of a page. `--candidates` lists every event these pages actually produced — each Form step and trigger, every `data-baker-*` event, every outbound destination — with volume and whether it is already counted; that list is where an event key comes from, so never invent one. `--event <key> --name "Booked a call"` starts counting it **immediately and retroactively**: the whole stored history is rescored, so naming the right event today also fixes last month. Nothing here is staged and publishing is not involved. Two events given the same `--name` become one row and one number, which is how a call booked on three different Forms reads as one outcome. `--count-mode every_time` is for the outcomes people genuinely repeat (a guide downloaded twice is two downloads); the default counts once per visit. One outcome per call — repeating `--event` is refused rather than silently keeping the last one.
|
|
1233
1254
|
|
|
1234
1255
|
**Campaign parameters are per platform, and so are the numbers.** `baker analytics ads` returns `adPlatformTraffic` (visits, conversions and *tagged* visits for each of the nine platforms), `adParams` for the platform in `--platform` only, and `adParamsUnattributed` for names arriving on visits with no click id and no recognisable source. Read `adPlatformTraffic` first: a platform with visits and zero tagged visits has untagged ad URLs, and no mapping can fix that from Baker's side. `baker analytics map` needs `--platform` for any change, accepts a parameter name **nobody has sent yet** — which is how a tracking template is configured before its campaign runs — and takes an answer back with `--remove`. `--set <name>=ignore` says a parameter is not campaign information at all, so Baker stops storing it and stops listing it.
|
|
1235
1256
|
|
|
@@ -1977,7 +1998,7 @@ Each external source is its own subcommand. Pick the verb that matches the sourc
|
|
|
1977
1998
|
|---|---|---|
|
|
1978
1999
|
| `baker images library <q>` | Hybrid library search (replaces `search`) | n/a |
|
|
1979
2000
|
| `baker images find <q>` | Fanout: library + opted-in providers | off |
|
|
1980
|
-
| `baker images stock <q> [--type photo\|vector
|
|
2001
|
+
| `baker images stock <q> [--type photo\|illustration\|vector]` | Free stock across two libraries — Pexels photographs, Pixabay illustrations and vectors | off |
|
|
1981
2002
|
| `baker images google <q>` | Google Images via the official Custom Search JSON API | off |
|
|
1982
2003
|
| `baker images pinterest <q>` | Pinterest reference imagery via ScrapeCreators (photo-real mood boards) | off |
|
|
1983
2004
|
| `baker images logo <domain>` | Brand logo via Brandfetch CDN | **on** (top 1) |
|
|
@@ -2002,7 +2023,7 @@ Each external source is its own subcommand. Pick the verb that matches the sourc
|
|
|
2002
2023
|
|
|
2003
2024
|
**Auto-ingest** runs the full `processImage` pipeline (Gemini describe + Voyage multimodal embed + OpenRouter text embed) on every hit. Override with `--auto-ingest N` (turn on) or `--no-auto-ingest` (turn off where default is on). When auto-ingest succeeds, the matching returned hit uses the Baker-owned URL and keeps the original provider URL as `sourceUrl`. After auto-ingest the next `baker images library` query for the same concept hits the local row.
|
|
2004
2025
|
|
|
2005
|
-
**Provider context biases the description.** When a hit carries human-readable text (Giphy `alt_text` / `title`,
|
|
2026
|
+
**Provider context biases the description.** When a hit carries human-readable text (Giphy `alt_text` / `title`, Pexels `alt`, Pixabay `tags`, Brandfetch type+theme, etc.) it's surfaced on the hit as `descriptionContext` and forwarded to Gemini during auto-ingest as a hint — Gemini still trusts the pixels (e.g. it'll correct a wrong botanical name) but uses the hint for cultural/brand/scene awareness it can't infer from bytes alone.
|
|
2006
2027
|
|
|
2007
2028
|
**`--context` is available on every command that adds an image to the library** — `ingest`, `use`, `upload`, `find`, `extract`, `screenshot`, `logo`, `stock`, `google`, `icon`, `gif`, `sticker`. On auto-ingest paths it **overrides** the provider-derived `descriptionContext` for every hit that gets ingested. Especially valuable for brand-vs-competitor disambiguation — Gemini cannot tell from pixels alone whether an icon belongs to "Stripe (integration partner)" vs "Stripe (competitor)". Concrete cases:
|
|
2008
2029
|
|
|
@@ -2017,6 +2038,8 @@ baker images screenshot https://competitor.com/pricing --context "Competitor pri
|
|
|
2017
2038
|
|
|
2018
2039
|
**`baker images search` is a deprecated alias** for `library` (stderr deprecation log, removed in next minor).
|
|
2019
2040
|
|
|
2041
|
+
**Migrating from Magnific:** `baker images stock` used to run on Magnific, Freepik's developer API. That provider is gone and the command now searches Pexels and Pixabay together. Consequences: it is free (no `image-magnific-search` credit line any more); `--type` survives as `photo|illustration|vector` (PSDs and mockups are the one thing genuinely lost); `--license`, `--ai`, `--people` and `--order` now fail with a fix naming the replacement rather than being silently dropped; and `--orientation` loses `panoramic` while `--size` and `--locale` are new. Library rows already ingested from Magnific keep `source: "magnific"`, stay searchable, and remain a valid `--source` filter; nothing writes that value any more.
|
|
2042
|
+
|
|
2020
2043
|
**Migrating from Tenor:** the old `baker images meme` command and Tenor provider have been removed. Use `baker images gif` (Giphy) instead. Existing library rows ingested via Tenor remain stored with `source: "tenor"` but are no longer returned by `baker images find` and `--source tenor` is no longer a valid filter on `library`. Re-ingest via `baker images gif --auto-ingest N` if you need fresh equivalents.
|
|
2021
2044
|
|
|
2022
2045
|
### `baker images library <query>`
|
|
@@ -2026,7 +2049,7 @@ Search the company library (hybrid BM25 + vector + Cohere rerank). Use this FIRS
|
|
|
2026
2049
|
```bash
|
|
2027
2050
|
baker images library "hero banner"
|
|
2028
2051
|
baker images library "logo" --aspect-ratio 1:1 --tags logo
|
|
2029
|
-
baker images library "office" --source
|
|
2052
|
+
baker images library "office" --source pexels
|
|
2030
2053
|
baker images library "pricing" --external-url-host competitor.com
|
|
2031
2054
|
```
|
|
2032
2055
|
|
|
@@ -2038,7 +2061,7 @@ baker images library "pricing" --external-url-host competitor.com
|
|
|
2038
2061
|
| `--min-score` | Minimum relevance score, 0-1 |
|
|
2039
2062
|
| `--aspect-ratio` | Filter by aspect ratio |
|
|
2040
2063
|
| `--tags` | Comma-separated tag names |
|
|
2041
|
-
| `--source` | Filter by source (uploaded,
|
|
2064
|
+
| `--source` | Filter by source (uploaded, pexels, pixabay, brandfetch, google_images, firecrawl, screenshotone, iconify, giphy, …). `magnific` still filters the rows the retired stock provider left behind |
|
|
2042
2065
|
| `--external-url-host` | Filter by host substring of `externalUrl` (e.g. competitor.com) |
|
|
2043
2066
|
| `--output` | Output format: `json` \| `files` \| `md` |
|
|
2044
2067
|
| `--fields` | Comma-separated field names to include |
|
|
@@ -2051,12 +2074,12 @@ baker images library "pricing" --external-url-host competitor.com
|
|
|
2051
2074
|
Fanout image search: library first, then opted-in providers in parallel.
|
|
2052
2075
|
|
|
2053
2076
|
```bash
|
|
2054
|
-
baker images find "office" --sources library,
|
|
2055
|
-
baker images find "office" --sources library,
|
|
2077
|
+
baker images find "office" --sources library,pexels,pixabay --limit 20
|
|
2078
|
+
baker images find "office" --sources library,pexels --fallback --threshold 0.4
|
|
2056
2079
|
baker images find "celebration" --sources library,giphy --auto-ingest 3
|
|
2057
2080
|
```
|
|
2058
2081
|
|
|
2059
|
-
Providers: `library`, `
|
|
2082
|
+
Providers: `library`, `pexels`, `pixabay`, `google`, `iconify`, `giphy`, `pinterest`. Brandfetch is not part of the fanout — it takes a domain, not a query, so it lives at `baker images logo <domain>`. (`pinterest` is photo-real/candid imagery — best for sourcing fresh people and sets, e.g. a recast creator or a pinned location in a video reproduction.) Response shape: `{ groups: { library, external }, ingested, meta: { counts, errors } }`. When `--auto-ingest` is used, `ingested[]` preserves successful external ingest order and matching external hits are enriched with the Baker-owned URL plus source provenance. Partial failures (one provider throws) return the successful providers plus a `meta.errors` array; the whole call never fails on a single provider error.
|
|
2060
2083
|
|
|
2061
2084
|
**Flags:**
|
|
2062
2085
|
|
|
@@ -2071,17 +2094,28 @@ Providers: `library`, `magnific`, `google`, `iconify`, `giphy`, `pinterest`. Bra
|
|
|
2071
2094
|
|
|
2072
2095
|
### `baker images stock <query>`
|
|
2073
2096
|
|
|
2074
|
-
|
|
2097
|
+
`stock` is **one verb over the free-stock class**, not one provider. It searches **Pexels** (`api.pexels.com`, raw key in `Authorization`, `PEXELS_API_KEY`) for photographs and **Pixabay** (`pixabay.com/api`, `key` query param, `PIXABAY_API_KEY`) for photographs plus illustrations and vector artwork, then interleaves and dedupes the two. Both APIs are free — the command costs nothing and reports no credit consumption.
|
|
2098
|
+
|
|
2099
|
+
They are one verb because they carry the same licence shape: everything either holds is free for commercial use, including a client's landing page and paid social, with no attribution required. They differ only in what they hold, and an agent should not have to know which library a picture lives in.
|
|
2075
2100
|
|
|
2076
2101
|
```bash
|
|
2077
2102
|
baker images stock "minimalist office"
|
|
2078
|
-
baker images stock "
|
|
2079
|
-
baker images stock "
|
|
2080
|
-
baker images stock "
|
|
2081
|
-
baker images stock "
|
|
2103
|
+
baker images stock "hero photo of a kitchen" --orientation landscape --size large
|
|
2104
|
+
baker images stock "flat office workers" --type illustration
|
|
2105
|
+
baker images stock "leaf outline mark" --type vector
|
|
2106
|
+
baker images stock "brand pattern" --color "#0a0a0a" --auto-ingest 2
|
|
2107
|
+
baker images stock "oficina luminosa" --locale es-ES
|
|
2082
2108
|
```
|
|
2083
2109
|
|
|
2084
|
-
|
|
2110
|
+
**A filter only one library understands narrows the search to that library** rather than being dropped — `convex/imageProviders/stock/plan.ts` resolves this once. `--type illustration|vector` and a *named* `--color` are Pixabay-only; `--orientation square` and a *hex* `--color` are Pexels-only. Querying a library without a filter it cannot honour would return an unfiltered result against a filtered request, which reads as a right answer.
|
|
2111
|
+
|
|
2112
|
+
**A partial result says so.** If one library fails the other still answers, and the response carries `errors[]` plus a `hints` entry naming the library that dropped out — so a thin result reads as unproven rather than as "stock does not have this".
|
|
2113
|
+
|
|
2114
|
+
Credits ride on each hit's `providerMeta`: Pexels `photographer` / `photographerUrl`, Pixabay `contributor`. Neither licence requires attribution, but both ask that the source be shown where results are displayed, and `externalUrl` is that link.
|
|
2115
|
+
|
|
2116
|
+
**Pixabay's tier decides what you actually get.** The full-resolution raster (`imageURL`) and the source SVG (`vectorURL`) need "full API access", requested free from Pixabay. Without it every Pixabay hit is the 1280px `largeImageURL`, so `--type vector` returns a raster of vector artwork rather than an SVG, and `--size` returns Pexels alone — the floor is applied to the file you receive, not to the original the library holds, because a 1.1MP download satisfying a 24MP request is how an agent ends up putting a soft image in a full-bleed hero. `width`/`height` on a hit describe the delivered file for the same reason. Granting full access fixes all three with no code change.
|
|
2117
|
+
|
|
2118
|
+
Pixabay's terms also require results to be cached 24h — the provider's `DAY` action cache is what satisfies that, so don't shorten it.
|
|
2085
2119
|
|
|
2086
2120
|
An empty result comes back with `hints` naming the filters that narrowed the search plus the fallbacks (`baker images find --sources …`, `baker studio generate`); a provider failure returns an `error.fix` pointing at the same exits. `baker images google` does the same, minus the cross-provider fallback (it is the last-resort provider).
|
|
2087
2121
|
|
|
@@ -2089,14 +2123,12 @@ An empty result comes back with `hints` naming the filters that narrowed the sea
|
|
|
2089
2123
|
|
|
2090
2124
|
| Flag | Description |
|
|
2091
2125
|
|------------------|--------------------------------------------------------------------------------------------|
|
|
2092
|
-
| `--type` |
|
|
2093
|
-
| `--orientation` | `landscape \| portrait \| square
|
|
2094
|
-
| `--
|
|
2095
|
-
| `--color` |
|
|
2096
|
-
| `--
|
|
2097
|
-
| `--
|
|
2098
|
-
| `--order` | `relevance` (default) or `recent` |
|
|
2099
|
-
| `--limit` | Max results (1–50, default 10) |
|
|
2126
|
+
| `--type` | `photo` (default, both libraries) \| `illustration` \| `vector` (Pixabay only) |
|
|
2127
|
+
| `--orientation` | `landscape \| portrait \| square` (`square` is Pexels only) |
|
|
2128
|
+
| `--size` | Minimum resolution: `large` (24MP) \| `medium` (12MP) \| `small` (4MP) |
|
|
2129
|
+
| `--color` | Colour name (`red`, `turquoise`, `black`, …; both libraries) or hex (`#0a0a0a`; Pexels only) |
|
|
2130
|
+
| `--locale` | Locale of the search terms, e.g. `es-ES`, `de-DE` — Pexels matches translated tags |
|
|
2131
|
+
| `--limit` | Max results (1–80, default 10) |
|
|
2100
2132
|
| `--page` | Page number for pagination |
|
|
2101
2133
|
| `--auto-ingest` | Ingest top N hits (0–20, default 0) and return Baker-owned URLs on ingested hits |
|
|
2102
2134
|
| `--context` | Free-text hint passed to Gemini describe to bias the generated description and tags (overrides provider-derived context on auto-ingest paths) |
|
|
@@ -2326,11 +2358,11 @@ If the target page is unreachable or returns a non-2xx status (e.g. a 404 path o
|
|
|
2326
2358
|
Download a remote URL and store it in the library. Byte-exact deduped via sha256 + externalId, then queued for describe + embed.
|
|
2327
2359
|
|
|
2328
2360
|
```bash
|
|
2329
|
-
baker images ingest https://
|
|
2361
|
+
baker images ingest https://images.pexels.com/photos/13219418/pexels-photo-13219418.jpeg --source pexels --external-id 13219418
|
|
2330
2362
|
baker images ingest https://acme.com/hero.png --source firecrawl --external-url https://acme.com/pricing --context "competitor pricing hero"
|
|
2331
2363
|
```
|
|
2332
2364
|
|
|
2333
|
-
`--source` is a closed set, validated before the request so a wrong value costs nothing: `uploaded`, `website`, `google_testimonial`, `trustpilot_testimonial`, `instagram`, `
|
|
2365
|
+
`--source` is a closed set, validated before the request so a wrong value costs nothing: `uploaded`, `website`, `google_testimonial`, `trustpilot_testimonial`, `instagram`, `pexels`, `pixabay`, `magnific` (retired provider, kept for existing rows), `brandfetch`, `google_images`, `firecrawl`, `screenshotone`, `iconify`, `giphy`, `pinterest`, `ai_generated`, `layer_edit`. Use `website` for an asset pulled off a company's own site.
|
|
2334
2366
|
|
|
2335
2367
|
Returns `{ imageId, deduped, contentHash }`. When `deduped: true`, an existing library row is returned — no new bytes are stored. Max ingest size 25MB.
|
|
2336
2368
|
|
|
@@ -2338,7 +2370,7 @@ Returns `{ imageId, deduped, contentHash }`. When `deduped: true`, an existing l
|
|
|
2338
2370
|
|
|
2339
2371
|
| Flag | Description |
|
|
2340
2372
|
|------------------|--------------------------------------------------------------------------------------------|
|
|
2341
|
-
| `--source` | **Required.** Source enum (`uploaded`, `
|
|
2373
|
+
| `--source` | **Required.** Source enum (`uploaded`, `pexels`, `pixabay`, `brandfetch`, `google_images`, `firecrawl`, `screenshotone`, `iconify`, `giphy`, `instagram`, …) |
|
|
2342
2374
|
| `--external-id` | Provider asset id — enables per-source dedup before bytes are fetched |
|
|
2343
2375
|
| `--external-url` | Canonical page URL (parent page for scraped/Google hits, provider page for stock) |
|
|
2344
2376
|
| `--context` | Free-text hint passed to Gemini describe to bias the generated description and tags |
|
|
@@ -2402,7 +2434,7 @@ Local-mode supported extensions: `.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`, `.svg
|
|
|
2402
2434
|
|
|
2403
2435
|
| Flag | Mode | Description |
|
|
2404
2436
|
|------------------|--------|--------------------------------------------------------------------------------------------|
|
|
2405
|
-
| `--source` | both | Source enum (default `uploaded`). For remote URLs prefer `firecrawl`, `
|
|
2437
|
+
| `--source` | both | Source enum (default `uploaded`). For remote URLs prefer `firecrawl`, `pexels`, etc. to match the origin |
|
|
2406
2438
|
| `--content-type` | local | MIME type override (default: inferred from file extension). Ignored for URLs |
|
|
2407
2439
|
| `--external-id` | remote | Provider asset id — enables per-source dedup before bytes are fetched |
|
|
2408
2440
|
| `--external-url` | remote | Canonical page URL (parent page for scraped/Google hits, provider page for stock) |
|
|
@@ -2729,6 +2761,15 @@ When `list` or `search` returns nothing, the envelope carries `meta.empty` expla
|
|
|
2729
2761
|
|
|
2730
2762
|
If the source check itself fails, `meta.empty` is omitted entirely and the hint says so — an unverified absence is never reported as one.
|
|
2731
2763
|
|
|
2764
|
+
`list` is paged. `--limit` is the page size (default 50); when the library continues past the page you got, the envelope carries `meta.next_cursor` and a hint, and `meta.empty` is omitted — a page that is not the last page establishes nothing about the corpus. Read on with the same filters plus `--cursor`:
|
|
2765
|
+
|
|
2766
|
+
```bash
|
|
2767
|
+
baker testimonials list --sentiment positive --limit 50
|
|
2768
|
+
baker testimonials list --sentiment positive --limit 50 --cursor <meta.next_cursor>
|
|
2769
|
+
```
|
|
2770
|
+
|
|
2771
|
+
No `meta.next_cursor` means the library ended there.
|
|
2772
|
+
|
|
2732
2773
|
### `baker testimonials tags`
|
|
2733
2774
|
|
|
2734
2775
|
List the available testimonial tag names — built-in defaults plus the company's custom tags. Use it before filtering with `--tags`. Defaults to a markdown list (`--output json` for the `{ ok, data }` envelope).
|
|
@@ -2779,7 +2820,7 @@ baker avatars create --name "Marta" --subject "…" \
|
|
|
2779
2820
|
--source-image "<imageId1>,<imageId2>" --likeness-confirmed
|
|
2780
2821
|
```
|
|
2781
2822
|
|
|
2782
|
-
Optional: `--handle`, `--speech`, `--motion`, `--setting`, `--
|
|
2823
|
+
Optional: `--handle`, `--speech`, `--motion`, `--setting`, `--accent`. Returns `{ avatarId, handle, likeness, status }`.
|
|
2783
2824
|
|
|
2784
2825
|
#### `baker avatars update <handle>`
|
|
2785
2826
|
|
|
@@ -2788,7 +2829,7 @@ Edit an avatar. Profile flags are merged over the stored profile, so `--persona`
|
|
|
2788
2829
|
```bash
|
|
2789
2830
|
baker avatars update marta --wardrobe "charcoal blazer over a white tee"
|
|
2790
2831
|
baker avatars update marta --subject "woman in her early 50s, silver bob, …" --regenerate-sheet
|
|
2791
|
-
baker avatars update marta --new-handle marta-founder --
|
|
2832
|
+
baker avatars update marta --new-handle marta-founder --accent "Spanish from Spain, Madrid accent"
|
|
2792
2833
|
```
|
|
2793
2834
|
|
|
2794
2835
|
#### `baker avatars delete <handle>`
|
|
@@ -3004,7 +3045,7 @@ baker scheduled-actions create --template baker-ads --name "Weekly ad review" --
|
|
|
3004
3045
|
Rules:
|
|
3005
3046
|
|
|
3006
3047
|
- `--cron` and `--run-at` are mutually exclusive.
|
|
3007
|
-
- `--run-at` must be an ISO UTC timestamp ending in `Z
|
|
3048
|
+
- `--run-at` must be an ISO UTC timestamp ending in `Z`, and still in the future — a time that has already passed is refused when the op is staged. If a one-off's time passes while the chat is still open, publishing creates the task **switched off** with the reason on it rather than dropping it, so it is never lost.
|
|
3008
3049
|
- Draft-created ids use `temp_sched_*`; use them with `get`, `update`, or `delete` before publish.
|
|
3009
3050
|
- Staged commands require `BAKER_CHAT_ID` to point to an `in_progress` chat.
|
|
3010
3051
|
- `trigger` rejects `temp_sched_*` and only accepts published scheduled-action ids.
|
|
@@ -3648,6 +3689,18 @@ because a video model garbles a wordmark every time:
|
|
|
3648
3689
|
the ad needs something other than the client's default.
|
|
3649
3690
|
- **The spoken language is read off the script** when the spec does not declare it, so a
|
|
3650
3691
|
Spanish ad is cast from Spanish voices without anyone having to say so.
|
|
3692
|
+
- **`sound`** (per beat) — one sound effect for that shot, rendered by ElevenLabs
|
|
3693
|
+
alongside the music bed. Most beats should not have one: it belongs where the picture
|
|
3694
|
+
shows a specific physical event (a drill, a door, rain on glass), not on a talking head
|
|
3695
|
+
or a calm lifestyle shot. A bed plus two well-placed effects beats eight.
|
|
3696
|
+
- **A real brand mark is required.** `scaffold-ad` refuses to build without one in
|
|
3697
|
+
`src/brand/logos/` (SVG or PNG): the mark is drawn on every frame and on the closing
|
|
3698
|
+
card, so a stand-in ships an ad wearing the wrong brand.
|
|
3699
|
+
- **An avatar with no pinned voice cannot speak on camera.** Their lines would be read by
|
|
3700
|
+
a separately cast voice, which is dubbing and looks like it.
|
|
3701
|
+
- **`voiceover: false`** — a music-led ad. No voice, no transcription: the `say` lines
|
|
3702
|
+
become on-screen text and are captioned straight from the script, so the words are
|
|
3703
|
+
exact. Give it `music` too.
|
|
3651
3704
|
- **`--avatar <handle>`** — the same flag as on `studio generate` / `studio animate`, and the way to cast a person into an ad. Overrides `cast.avatar` in the spec.
|
|
3652
3705
|
- **`cast.avatar`** — the handle of a cast Avatar (`baker avatars list`). The ad grounds
|
|
3653
3706
|
every beat they appear in on that avatar's identity sheet and copies its subject
|
|
@@ -3669,6 +3722,9 @@ paid for by the time it runs — and the findings come back as `review` and `hin
|
|
|
3669
3722
|
- **Across the reel:** whether the cast stays the same people, whether a brand mark is
|
|
3670
3723
|
visible anywhere, and whether the last frames close the ad or simply stop.
|
|
3671
3724
|
|
|
3725
|
+
Each per-frame finding names the **scene file** that produced that second, so a defect is
|
|
3726
|
+
an edit rather than a hunt — and re-running re-renders only the scenes you changed.
|
|
3727
|
+
|
|
3672
3728
|
It needs `GEMINI_API_KEY` (the Runtime receives it automatically). Without one the run
|
|
3673
3729
|
says the video was **not** reviewed rather than reporting it clean, and a frame that
|
|
3674
3730
|
could not be read is reported as unchecked for the same reason.
|
|
@@ -4276,7 +4332,7 @@ There is no `prompt` param — the extraction schema is the node. Use `focus` to
|
|
|
4276
4332
|
|
|
4277
4333
|
##### `image_search`
|
|
4278
4334
|
|
|
4279
|
-
Search the web for real images instead of generating them. A backend LLM agent picks among Google Images, stock photography (
|
|
4335
|
+
Search the web for real images instead of generating them. A backend LLM agent picks among Google Images, free stock photography (Pexels and Pixabay), and Pinterest, refines queries, selects the best matches, and the chosen images are downloaded into canvas assets. Use it to gather references or candidates (e.g. five photos of an australian shepherd) for a later pick-the-best step.
|
|
4280
4336
|
|
|
4281
4337
|
**Inputs** — none.
|
|
4282
4338
|
|
|
@@ -5859,6 +5915,30 @@ Pushing to `main` with changes in `packages/cli/` triggers the GitHub Actions wo
|
|
|
5859
5915
|
./scripts/publish-package.sh cli next # Publish as @next (pre-release)
|
|
5860
5916
|
```
|
|
5861
5917
|
|
|
5918
|
+
- **0.243.0**: `baker ads linkedin account url-params` stages the URL tracking parameters on the whole **ad account** — the "Account tracking parameters" box in Campaign Manager, which the CLI (and this README, and the playbook) said was UI-only and unreachable through the API. It was, until LinkedIn's 202608 added `sponsoredAccount` to the `adEntity` key of `/adTrackingParameters`; Baker's LinkedIn version pin moves 202606 → 202608 with it. Same flags as the ad-set form (`--param`, `--dynamic`, `--clear`, `--file`) and no positional: the target is built from `--account-id`, because a bare numeric id has always meant an ad set and reading one as an account would silently stage the wrong write. **LinkedIn concatenates the two levels rather than overriding**, so a key set at both appears twice in the served URL — the stage warnings now name the other level for exactly that check, and the staged change says which level it applies to. `baker ads linkedin account` keeps reading account detail unchanged (it is now `account detail`, kept as the default subcommand).
|
|
5919
|
+
|
|
5920
|
+
- **0.244.0**: `baker ads linkedin url-params` **reads** the tracking parameters in effect — the ad account's, optionally every ad set's, and the keys set at BOTH levels. There was no read at all before: the only way to see what an entity carried was to stage a change and look at the before-state, which meant the concatenation trap (a key at the account AND an ad set lands in the served URL twice) was invisible until a report came back split across two values of the same parameter. Account-only by default because `adTrackingParameters` has no batch read, so each ad set is its own call; `--ad-sets` sweeps them, capped at 50, and the response carries `adSetCoverage: {examined, total, capped}` so a partial sweep can't read as a clean account. `--collisions-only` for just the finding.
|
|
5921
|
+
|
|
5922
|
+
- **0.245.0**: **a repeated flag no longer loses everything but its last value.** citty declares no array arg type — it calls `util.parseArgs` without `multiple: true` — so `--param utm_source=linkedin --param utm_medium=paid` reached the command as the single string `utm_medium=paid`. The arg descriptions said "repeatable", the unit tests passed arrays citty never produces, and `url-params` REPLACES an entity's whole parameter set: staging three UTMs set one and deleted the rest at publish, reporting success throughout. Found by staging against the real ad account and reading the payload back, not by any test. Every repeatable flag now reads its values off the command line: LinkedIn `--param`/`--dynamic`, Google Ads `--custom-param` (Google replaces the set wholesale too) and `--text`, `baker mcp add --header`, and `baker flows map --map`, which had already solved this locally and is now the shared helper.
|
|
5923
|
+
|
|
5924
|
+
- **0.245.0**: `baker ads linkedin url-params --campaign-ids` says when an id names no ad set. LinkedIn answers NOT_FOUND identically for "this ad set carries no parameters" and "there is no such ad set", so a mistyped id came back as an ad set that exists and is empty — and the next move on that is a write that only fails at publish. Such ids are now reported under `unreadable` with the reason, and the "nothing is tagged" hint is suppressed, exactly as for a level that could not be read.
|
|
5925
|
+
|
|
5926
|
+
- **0.245.0**: a backend condition the caller can act on reaches the caller. "Analytics reporting is not switched on for this workspace yet" was thrown as an `INTERNAL`, and the HTTP layer withholds the message of every 5xx so a real fault cannot leak its internals — so the CLI printed `INTERNAL_ERROR: Internal server error` and, worse, LinkedIn and Meta commands marked it retryable. It is now a `CONFLICT`, which the CLI maps to a new non-retryable `CONFLICT` code and prints verbatim.
|
|
5927
|
+
|
|
5928
|
+
- **0.246.0**: `baker ads linkedin draft` reports what a publish DID. It used to render a published draft exactly like a staged one — same "1 staged change", same tree, no mention of the outcome — even though the per-op `result` was in the response the whole time. So the one surface an agent reaches for to check a publish could not answer, and a publish that changed LinkedIn and then failed to finish looked identical to one that had not run yet. It now reads `published · 1 applied` / `2 applied, 1 failed`, names each failed or skipped op with its error, and says `publishing` while one is still in flight. Found because a tracking-parameter publish did exactly that: applied at LinkedIn, then left the draft stuck, with the only trace an `Error calling onComplete` in the server log.
|
|
5929
|
+
|
|
5930
|
+
- **0.247.0**: a tracking-parameter write with no `--dynamic` no longer fails at publish. LinkedIn's `/adTrackingParameters` record requires BOTH maps and rejects a body missing either — `ERROR :: /dynamicValueParameters :: field is required but not found and has no default value` — so the most ordinary write there is, a couple of fixed UTMs and no placeholder, was a hard failure, while every write that happened to carry a `--dynamic` succeeded. Both maps are now always sent, empty when unused. Found by the 0.246.0 outcome line, on the first real publish that did not pass `--dynamic`.
|
|
5931
|
+
|
|
5932
|
+
- **0.248.0**: a tracking-parameter read that fails no longer reads as "there is nothing there". The idempotency token is LinkedIn's concurrency guard AND the only look at the before-state, and `url-params` REPLACES the entity's whole set — so writing without it is not a degraded write, it is an unguarded one that overwrites live parameters having never seen them. Both readers collapsed every failure into "none set": the executor logged `not readable; writing as a fresh set` and did exactly that, and the stage-time snapshot logged `previewing as none set`, which silently removed the drop warning that exists to catch this. Only LinkedIn's NOT_FOUND now means "none yet" (a first write — and no longer logged, since it is the normal case that was filling the log). Any other failure fails the publish, and at stage time produces a warning saying the current parameters could not be read rather than none being there. It matters most on the 409 retry: a 409 means somebody changed the parameters between our read and our write, and a swallowed failure on the re-read turned that detected conflict into a blind overwrite of their change.
|
|
5933
|
+
|
|
5934
|
+
Reading it back changes three things beyond the command. **`campaigns url-params` / `account url-params` now warn by name when a write would delete a live key** — LinkedIn replaces an entity's whole parameter set rather than merging into it, so staging one pair on an ad set that already carries three published as "one pair, and the other three are gone". Pairs accumulate inside a chat, which is exactly what made a merge at publish the natural expectation; the stage warning names what would be dropped, and the guidance now says to read first and re-send everything that should survive. **`baker analytics map` can check a proposed LinkedIn mapping against the account** — LinkedIn was on the list of platforms with no configuration read, so its mappings shipped with no evidence behind them; it now reads the account level and up to ten ad sets. And **the `tracking.campaign_parameters` audit check answers before a campaign runs**: with no traffic it used to be `n_a`, honest but useless at the one moment the fix is free, and it now judges the account's own parameters — with wording that says so rather than claiming clicks arrived carrying nothing when no click arrived.
|
|
5935
|
+
|
|
5936
|
+
Three more things the 202608 pin brought with it. **The conversion-type enum went from 8 values to all 34 LinkedIn accepts** — `QUALIFIED_LEAD`, `BOOK_APPOINTMENT`, `SUBMIT_APPLICATION` and 23 others were rejected by our own schema before the request ever left, and 202608 adds `MARKETING_QUALIFIED_LEAD` / `SALES_QUALIFIED_LEAD` on top. **Attribution windows gained 365** (and view-through gained 90), with the type-conditional rule enforced on `conversions create`: 365 is only legal for `SUBMIT_APPLICATION`, `PURCHASE`, `ADD_TO_CART`, `QUALIFIED_LEAD`, `LEAD`. And **a 429 carrying `SEGMENT_LIMIT_EXCEEDED` is no longer reported as a throttle** — 202608 caps an ad account at 1,000 DMP segments and reuses the rate-limit status for it, so an agent that read it as "retry in a second" would have retried forever against a cap only a human can clear in Campaign Manager.
|
|
5937
|
+
|
|
5938
|
+
- **0.256.0**: **`assets attach` no longer offers an image link, because Google accepts none.** A production publish came back with six `AssetLinkError.FIELD_TYPE_INCOMPATIBLE_WITH_CAMPAIGN_TYPE` rows (trigger `MARKETING_IMAGE,SEARCH`) on an otherwise built and live Search campaign — staged exactly as this CLI's own advisory and the scaffold's ads-google doc instructed, at `"fieldType": "MARKETING_IMAGE"`. The obvious fix was `AD_IMAGE`, which is what Google's documentation and its own forum answers name for a Search image extension. **It does not work either.** Sent to a real account on v23, all four combinations are refused: `MARKETING_IMAGE` on a Search `CampaignAsset` → `FIELD_TYPE_INCOMPATIBLE_WITH_CAMPAIGN_TYPE`; `MARKETING_IMAGE` on a Search `AdGroupAsset` → `UNSUPPORTED_FIELD_TYPE`; `AD_IMAGE` on a `CampaignAsset` → `UNSUPPORTED_FIELD_TYPE`; `AD_IMAGE` on an `AdGroupAsset` → `UNSUPPORTED_FIELD_TYPE`. Image extensions never moved to asset-based extensions, so there is no request the API takes. Every image field type is now refused at the CLI boundary with the interface named instead of another field type to try, and the **"recommend at least 3 image assets" advisory is removed** — it had been firing on every Search campaign since 0.191.0 and could not be satisfied by any draft, because the write it named has never once succeeded. Performance Max asset-group images are a different mechanism and are untouched (`asset-groups attach`). Four force-synced scaffold surfaces were saying the same wrong thing and now agree with the API: the `ads-google.md` tool doc, the `assets` command group description, the account-audit playbook's Dynamic Images row, and `staged-writes.md`. An audit should still raise a missing-images gap — it is a real SERP disadvantage — it just has to name the Google Ads interface as the fix rather than a command.
|
|
5939
|
+
|
|
5940
|
+
- **0.256.0**: a structured snippet value over 25 characters is refused before it is staged. Google caps each VALUE at 25 and answers a longer one with a bare `Too long.` at `structured_snippet_asset.values[i]` — no header, no value, nothing to act on — and it takes the asset's campaign link down with it as `RESOURCE_NOT_FOUND`. `assets create --values` and `assets update --values` now cap each entry; the per-asset 3-10 count check was already there, and only the per-value length was missing. Found on a live publish where four of five values were fine and "Bases de datos vectoriales" (26) was not, and confirmed fixed by re-publishing the same snippet at 25 characters against a real account, where it applied and linked cleanly.
|
|
5941
|
+
|
|
5862
5942
|
### Testing a pre-release in sandboxes
|
|
5863
5943
|
|
|
5864
5944
|
```bash
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ApiError,
|
|
3
3
|
apiGet
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-KDTHRRAC.js";
|
|
5
5
|
import {
|
|
6
6
|
getEnv
|
|
7
7
|
} from "./chunk-WFWU3CHS.js";
|
|
@@ -295,4 +295,4 @@ export {
|
|
|
295
295
|
writeAdsOutput,
|
|
296
296
|
resolveCustomerId
|
|
297
297
|
};
|
|
298
|
-
//# sourceMappingURL=chunk-
|
|
298
|
+
//# sourceMappingURL=chunk-ARUM5SYE.js.map
|
|
@@ -2,10 +2,10 @@ import {
|
|
|
2
2
|
handleConnectionError,
|
|
3
3
|
needsConnectionFix,
|
|
4
4
|
writeAdsJson
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-ARUM5SYE.js";
|
|
6
6
|
import {
|
|
7
7
|
ApiError
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-KDTHRRAC.js";
|
|
9
9
|
import {
|
|
10
10
|
getEnv
|
|
11
11
|
} from "./chunk-WFWU3CHS.js";
|
|
@@ -108,4 +108,4 @@ export {
|
|
|
108
108
|
csvOrJson,
|
|
109
109
|
resolveEffectiveStatus
|
|
110
110
|
};
|
|
111
|
-
//# sourceMappingURL=chunk-
|
|
111
|
+
//# sourceMappingURL=chunk-IK3GODKH.js.map
|
|
@@ -79,6 +79,9 @@ function mapHttpError(status) {
|
|
|
79
79
|
if (status === 422 || status === 400) {
|
|
80
80
|
return "VALIDATION_ERROR";
|
|
81
81
|
}
|
|
82
|
+
if (status === 409) {
|
|
83
|
+
return "CONFLICT";
|
|
84
|
+
}
|
|
82
85
|
if (status === 429) {
|
|
83
86
|
return "RATE_LIMITED";
|
|
84
87
|
}
|
|
@@ -107,7 +110,7 @@ async function handleResponse(response) {
|
|
|
107
110
|
throw new ApiError("INTERNAL_ERROR", "Failed to parse API response as JSON");
|
|
108
111
|
}
|
|
109
112
|
}
|
|
110
|
-
async function
|
|
113
|
+
async function apiGetWithHeaders(path, params) {
|
|
111
114
|
const env = getEnv();
|
|
112
115
|
const url = new URL(path, env.BAKER_API_URL);
|
|
113
116
|
if (params) {
|
|
@@ -145,7 +148,10 @@ async function apiGet(path, params) {
|
|
|
145
148
|
responseBody: await readBodyForLog(response),
|
|
146
149
|
durationMs: Date.now() - startedAt
|
|
147
150
|
});
|
|
148
|
-
return handleResponse(response);
|
|
151
|
+
return { data: await handleResponse(response), headers: response.headers };
|
|
152
|
+
}
|
|
153
|
+
async function apiGet(path, params) {
|
|
154
|
+
return (await apiGetWithHeaders(path, params)).data;
|
|
149
155
|
}
|
|
150
156
|
async function apiPost(path, body, opts) {
|
|
151
157
|
const env = getEnv();
|
|
@@ -195,7 +201,8 @@ async function apiPost(path, body, opts) {
|
|
|
195
201
|
export {
|
|
196
202
|
ApiError,
|
|
197
203
|
validateConvexId,
|
|
204
|
+
apiGetWithHeaders,
|
|
198
205
|
apiGet,
|
|
199
206
|
apiPost
|
|
200
207
|
};
|
|
201
|
-
//# sourceMappingURL=chunk-
|
|
208
|
+
//# sourceMappingURL=chunk-KDTHRRAC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["import { debugLogHttp, readBodyForLog } from \"./debugLog.ts\";\nimport { getEnv } from \"./env.ts\";\n\nconst MAX_RATE_LIMIT_RETRIES = 3;\nconst MAX_TOTAL_WAIT_MS = 2 * 60 * 1000;\n\nasync function fetchWithRateLimitRetry(url: string, init: RequestInit): Promise<Response> {\n let totalWaited = 0;\n\n for (let attempt = 0; attempt <= MAX_RATE_LIMIT_RETRIES; attempt++) {\n const response = await fetch(url, init);\n\n if (response.status !== 429 || attempt >= MAX_RATE_LIMIT_RETRIES) {\n return response;\n }\n\n const retryAfterHeader = response.headers.get(\"Retry-After\");\n const waitMs = retryAfterHeader ? Number(retryAfterHeader) * 1000 : 2000 * 2 ** attempt;\n\n if (totalWaited + waitMs > MAX_TOTAL_WAIT_MS) {\n return response;\n }\n\n totalWaited += waitMs;\n await new Promise((resolve) => setTimeout(resolve, waitMs));\n }\n\n return fetch(url, init);\n}\n\ntype ErrorCode =\n | \"UNAUTHORIZED\"\n | \"FORBIDDEN\"\n | \"NOT_FOUND\"\n | \"CONFLICT\"\n | \"VALIDATION_ERROR\"\n | \"RATE_LIMITED\"\n | \"INTERNAL_ERROR\"\n | \"NETWORK_ERROR\"\n | \"TIMEOUT\"\n | \"IMAGE_PROCESSING_ERROR\";\n\nexport class ApiError extends Error {\n code: ErrorCode;\n\n constructor(code: ErrorCode, message: string) {\n super(message);\n this.name = \"ApiError\";\n this.code = code;\n }\n}\n\nconst CONVEX_ID_RE = /^[a-zA-Z0-9_]+$/;\n\nfunction hasControlCharacters(value: string): boolean {\n for (let i = 0; i < value.length; i++) {\n const code = value.charCodeAt(i);\n // Allow tab (9), newline (10), carriage return (13)\n if (code < 32 && code !== 9 && code !== 10 && code !== 13) {\n return true;\n }\n }\n return false;\n}\n\nfunction validateStringValue(value: string): void {\n if (hasControlCharacters(value)) {\n throw new ApiError(\"VALIDATION_ERROR\", \"String value contains invalid control characters\");\n }\n}\n\nexport function validateConvexId(id: string): void {\n if (!CONVEX_ID_RE.test(id)) {\n throw new ApiError(\"VALIDATION_ERROR\", `Invalid ID format: \"${id}\". Expected alphanumeric string.`);\n }\n}\n\nfunction sanitizeParams(params: Record<string, string>): Record<string, string> {\n const sanitized: Record<string, string> = {};\n for (const [key, value] of Object.entries(params)) {\n validateStringValue(value);\n sanitized[key] = value;\n }\n return sanitized;\n}\n\n/**\n * Sent on every call so the backend can attribute immediately-applied writes\n * (library images and videos, followed advertisers) to the Session they were\n * made from. Absent outside a chat-attached environment.\n */\nfunction chatHeader(): Record<string, string> {\n const chatId = getEnv().BAKER_CHAT_ID;\n return chatId ? { \"x-baker-chat-id\": chatId } : {};\n}\n\nfunction mapHttpError(status: number): ErrorCode {\n if (status === 401) {\n return \"UNAUTHORIZED\";\n }\n // 403 is never \"not connected\" — every backend `FORBIDDEN` means connected but\n // not allowed (an asset outside the granted scope, a capability the account\n // lacks). Folding it into UNAUTHORIZED sent agents down the reconnect path,\n // which cannot fix a permission gap and wastes the user's time.\n if (status === 403) {\n return \"FORBIDDEN\";\n }\n if (status === 404) {\n return \"NOT_FOUND\";\n }\n if (status === 422 || status === 400) {\n return \"VALIDATION_ERROR\";\n }\n // 409 is a state the caller has to change, never a fault to sit out. It is\n // what the backend returns for \"this workspace has not switched analytics on\n // yet\" and its siblings — folded into INTERNAL_ERROR, that reaches an agent\n // as a transient failure worth retrying, which it never is.\n if (status === 409) {\n return \"CONFLICT\";\n }\n if (status === 429) {\n return \"RATE_LIMITED\";\n }\n return \"INTERNAL_ERROR\";\n}\n\nasync function handleResponse<T>(response: Response): Promise<T> {\n const body = await response.text();\n\n if (!response.ok) {\n let message = `HTTP ${response.status}: ${response.statusText}`;\n try {\n const parsed = JSON.parse(body) as { error?: string | { message?: string }; message?: string };\n if (typeof parsed.error === \"string\") {\n message = parsed.error;\n } else if (parsed.error?.message) {\n message = parsed.error.message;\n } else if (parsed.message) {\n message = parsed.message;\n }\n } catch {\n // Use default message\n }\n throw new ApiError(mapHttpError(response.status), message);\n }\n\n try {\n return JSON.parse(body) as T;\n } catch {\n throw new ApiError(\"INTERNAL_ERROR\", \"Failed to parse API response as JSON\");\n }\n}\n\n/**\n * A GET that also hands back the response headers.\n *\n * Some endpoints have to say something *about* the answer that the answer has\n * no room for — `GET /api/testimonials` returns a bare array and sets\n * `X-Baker-Next-Cursor` when rows past this page remain unexamined, so the rows\n * alone cannot tell one page of a library from the whole of it.\n */\nexport async function apiGetWithHeaders<T>(\n path: string,\n params?: Record<string, string>,\n): Promise<{ data: T; headers: Headers }> {\n const env = getEnv();\n const url = new URL(path, env.BAKER_API_URL);\n if (params) {\n const clean = sanitizeParams(params);\n for (const [key, value] of Object.entries(clean)) {\n url.searchParams.set(key, value);\n }\n }\n\n const urlStr = url.toString();\n const startedAt = Date.now();\n let response: Response;\n try {\n response = await fetchWithRateLimitRetry(urlStr, {\n method: \"GET\",\n headers: {\n Authorization: `Bearer ${env.BAKER_API_KEY}`,\n Accept: \"application/json\",\n ...chatHeader(),\n },\n signal: AbortSignal.timeout(60_000),\n });\n } catch (err) {\n debugLogHttp({ source: \"cli\", method: \"GET\", url: urlStr, durationMs: Date.now() - startedAt, error: err });\n if (err instanceof Error && (err.name === \"TimeoutError\" || err.name === \"AbortError\")) {\n throw new ApiError(\"TIMEOUT\", \"Request timed out after 60 seconds\");\n }\n throw new ApiError(\"NETWORK_ERROR\", `Request failed: ${err instanceof Error ? err.message : \"Unknown error\"}`);\n }\n\n debugLogHttp({\n source: \"cli\",\n method: \"GET\",\n url: urlStr,\n status: response.status,\n ok: response.ok,\n responseBody: await readBodyForLog(response),\n durationMs: Date.now() - startedAt,\n });\n return { data: await handleResponse<T>(response), headers: response.headers };\n}\n\nexport async function apiGet<T>(path: string, params?: Record<string, string>): Promise<T> {\n return (await apiGetWithHeaders<T>(path, params)).data;\n}\n\nexport async function apiPost<T>(path: string, body: unknown, opts?: { timeoutMs?: number }): Promise<T> {\n const env = getEnv();\n const timeoutMs = opts?.timeoutMs ?? 60_000;\n const urlStr = new URL(path, env.BAKER_API_URL).toString();\n const startedAt = Date.now();\n let response: Response;\n try {\n response = await fetchWithRateLimitRetry(urlStr, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${env.BAKER_API_KEY}`,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n ...chatHeader(),\n },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(timeoutMs),\n });\n } catch (err) {\n debugLogHttp({\n source: \"cli\",\n method: \"POST\",\n url: urlStr,\n requestBody: body,\n durationMs: Date.now() - startedAt,\n error: err,\n });\n if (err instanceof Error && (err.name === \"TimeoutError\" || err.name === \"AbortError\")) {\n throw new ApiError(\"TIMEOUT\", `Request timed out after ${Math.round(timeoutMs / 1000)} seconds`);\n }\n throw new ApiError(\"NETWORK_ERROR\", `Request failed: ${err instanceof Error ? err.message : \"Unknown error\"}`);\n }\n\n debugLogHttp({\n source: \"cli\",\n method: \"POST\",\n url: urlStr,\n requestBody: body,\n status: response.status,\n ok: response.ok,\n responseBody: await readBodyForLog(response),\n durationMs: Date.now() - startedAt,\n });\n return handleResponse<T>(response);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -17,9 +17,9 @@ import {
|
|
|
17
17
|
shouldEscalate
|
|
18
18
|
} from "./chunk-WFWU3CHS.js";
|
|
19
19
|
|
|
20
|
-
// ../../.pnpm-store/v10/links/@/safe-stable-stringify/2.5.0/
|
|
20
|
+
// ../../.pnpm-store/v10/links/@/safe-stable-stringify/2.5.0/88b6d0c76c4dff408de4044be85791896193f2435af7c280bc59312c3fb9649c/node_modules/safe-stable-stringify/index.js
|
|
21
21
|
var require_safe_stable_stringify = __commonJS({
|
|
22
|
-
"../../.pnpm-store/v10/links/@/safe-stable-stringify/2.5.0/
|
|
22
|
+
"../../.pnpm-store/v10/links/@/safe-stable-stringify/2.5.0/88b6d0c76c4dff408de4044be85791896193f2435af7c280bc59312c3fb9649c/node_modules/safe-stable-stringify/index.js"(exports, module) {
|
|
23
23
|
"use strict";
|
|
24
24
|
var { hasOwnProperty } = Object.prototype;
|
|
25
25
|
var stringify = configure2();
|
|
@@ -1098,7 +1098,7 @@ function resolveAdaptFormats(params) {
|
|
|
1098
1098
|
return params.formats ?? [];
|
|
1099
1099
|
}
|
|
1100
1100
|
|
|
1101
|
-
// ../../.pnpm-store/v10/links/@/safe-stable-stringify/2.5.0/
|
|
1101
|
+
// ../../.pnpm-store/v10/links/@/safe-stable-stringify/2.5.0/88b6d0c76c4dff408de4044be85791896193f2435af7c280bc59312c3fb9649c/node_modules/safe-stable-stringify/esm/wrapper.js
|
|
1102
1102
|
var import__ = __toESM(require_safe_stable_stringify(), 1);
|
|
1103
1103
|
var configure = import__.default.configure;
|
|
1104
1104
|
var wrapper_default = import__.default;
|
|
@@ -3141,7 +3141,7 @@ var VEO_DURATIONS = [4, 6, 8];
|
|
|
3141
3141
|
var KLING_DURATIONS2 = [5, 10];
|
|
3142
3142
|
var KLING_NEGATIVE_PROMPT = "warped face, distorted hands, extra fingers, morphing, flicker, on-screen text, watermark, low quality";
|
|
3143
3143
|
var KLING_CFG_SCALE = 0.7;
|
|
3144
|
-
var SPEAKS_PROSE = (line) => `The person speaks to camera; lip-sync follows the dialogue verbatim, with delivery and emotion carried in the wording itself (no bracketed cues)
|
|
3144
|
+
var SPEAKS_PROSE = (line, accent) => `The person speaks to camera; lip-sync follows the dialogue verbatim, with delivery and emotion carried in the wording itself (no bracketed cues).${accent ? ` The speech is in ${accent}.` : ""} Dialogue: "${line}"`;
|
|
3145
3145
|
var SEEDANCE_PROFILE = {
|
|
3146
3146
|
id: "seedance",
|
|
3147
3147
|
dialogueDirective: SPEAKS_PROSE,
|
|
@@ -7349,17 +7349,18 @@ var GATE_SECTIONS = ["lint", "runtime", "layout", "motion", "contrast"];
|
|
|
7349
7349
|
function buildCheckArgs(dir, samples) {
|
|
7350
7350
|
return ["hyperframes", "check", dir, "--json", "--samples", String(samples), "--at-transitions"];
|
|
7351
7351
|
}
|
|
7352
|
-
var PROMOTE_TO_BLOCKING = /GSAP target [^ ]+ not found/i;
|
|
7352
|
+
var PROMOTE_TO_BLOCKING = /GSAP target [^ ]+ not found|no deterministic font mapping/i;
|
|
7353
7353
|
function toGateFinding(section, entry) {
|
|
7354
7354
|
const f = entry;
|
|
7355
7355
|
const code = typeof f.code === "string" ? f.code : "unknown";
|
|
7356
7356
|
const message2 = typeof f.message === "string" ? f.message : "";
|
|
7357
7357
|
const isError = f.severity === "error" || PROMOTE_TO_BLOCKING.test(message2);
|
|
7358
|
+
const fatal = PROMOTE_TO_BLOCKING.test(message2);
|
|
7358
7359
|
return {
|
|
7359
7360
|
source: section,
|
|
7360
7361
|
code,
|
|
7361
7362
|
message: message2,
|
|
7362
|
-
severity: isError && !isAdvisory(code, message2) ? "blocking" : "warning"
|
|
7363
|
+
severity: fatal || isError && !isAdvisory(code, message2) ? "blocking" : "warning"
|
|
7363
7364
|
};
|
|
7364
7365
|
}
|
|
7365
7366
|
function classifyGateOutput(raw) {
|
|
@@ -8477,7 +8478,7 @@ var imageSearchNode = delegated({
|
|
|
8477
8478
|
id: "image_search",
|
|
8478
8479
|
version: "1.0.0",
|
|
8479
8480
|
category: "image",
|
|
8480
|
-
summary: "Agentic image search across Google Images, stock photography (
|
|
8481
|
+
summary: "Agentic image search across Google Images, free stock photography (Pexels and Pixabay), and Pinterest. An LLM agent picks the search tools and queries, selects the best matches, and the results are downloaded into canvas assets.",
|
|
8481
8482
|
when_to_use: "Use to gather real-world reference or inspiration images for a prompt (e.g. several photos of an australian shepherd) so a later step or the user can pick the best one. Not for creating new imagery \u2014 use image_generate for that.",
|
|
8482
8483
|
inputs: z23.object({}).loose(),
|
|
8483
8484
|
params: ImageSearchParams,
|
|
@@ -9044,7 +9045,14 @@ async function tryExtractAudio(inputs, ctx) {
|
|
|
9044
9045
|
import { z as z34 } from "zod";
|
|
9045
9046
|
var voiceSelectNode = delegated({
|
|
9046
9047
|
id: "voice_select",
|
|
9047
|
-
|
|
9048
|
+
// 1.1.0: a pinned voice used to come back as an inline `{ kind, text }` with no sha256
|
|
9049
|
+
// and no mime, which every consumer taking the id as an INPUT rejected —
|
|
9050
|
+
// `audio_voice_convert` died on `voice_ref.sha256: expected string, received undefined`.
|
|
9051
|
+
// The backend now uploads it as a real asset like the ranked branch, and the version has
|
|
9052
|
+
// to move with it: the cache key is built from this string, so entries written under
|
|
9053
|
+
// 1.0.0 kept replaying the broken shape long after the fix was deployed. A node whose
|
|
9054
|
+
// OUTPUT shape changes has to bump, not only one whose behaviour does.
|
|
9055
|
+
version: "1.1.0",
|
|
9048
9056
|
category: "audio",
|
|
9049
9057
|
summary: 'Cast an ElevenLabs voice from a natural-language description (e.g. "warm, authoritative female narrator, American accent"). Lists the account\'s voices and ranks them against the brief, emitting the best `voice_id` as a bare-string text asset plus a ranked `candidates` JSON.',
|
|
9050
9058
|
when_to_use: 'Use to turn a voice description (e.g. from a `video_deconstruct` blueprint\'s `voice_description`) into a usable ElevenLabs voice id, then feed it into a `tts` node by wiring `inputs.voice_ref: $ref:<this>.voice_id` and setting `params.voice: "{{voice_ref}}"` \u2014 the engine splices the id in at run time. Review `candidates` (json) to pick a different voice. Optional `gender`/`age`/`accent`/`language` hints sharpen the ranking.',
|
|
@@ -9055,6 +9063,11 @@ var voiceSelectNode = delegated({
|
|
|
9055
9063
|
age: z34.string().optional(),
|
|
9056
9064
|
accent: z34.string().optional(),
|
|
9057
9065
|
language: z34.string().optional(),
|
|
9066
|
+
/**
|
|
9067
|
+
* A voice already chosen — an Avatar's pinned one. Given, nothing is cast: the
|
|
9068
|
+
* ranking is skipped and this id is emitted as-is.
|
|
9069
|
+
*/
|
|
9070
|
+
voice_id: z34.string().min(1).max(120).optional(),
|
|
9058
9071
|
limit: z34.number().int().min(1).max(20).optional()
|
|
9059
9072
|
}).strict(),
|
|
9060
9073
|
outputs: z34.object({ voice_id: TextRef, candidates: JsonRef }).strict(),
|
|
@@ -9272,4 +9285,4 @@ export {
|
|
|
9272
9285
|
defaultRegistry,
|
|
9273
9286
|
createEngineFromEnv
|
|
9274
9287
|
};
|
|
9275
|
-
//# sourceMappingURL=chunk-
|
|
9288
|
+
//# sourceMappingURL=chunk-LDDTPUYV.js.map
|