@koda-sl/baker-cli 0.156.0 → 0.158.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -160,6 +160,34 @@ baker ads google currency --customer-id 5904042878
160
160
 
161
161
  ---
162
162
 
163
+ ### `baker ads google url-options`
164
+
165
+ The UTM / tracking parameters in force on the account: the account-level Final URL Suffix and tracking template, plus every campaign that overrides them. Start here for any "add `utm_agency=…`" or agency-tag question.
166
+
167
+ ```bash
168
+ baker ads google url-options --customer-id 1234567890 # overriding campaigns only
169
+ baker ads google url-options --customer-id 1234567890 --full # every campaign read
170
+ ```
171
+
172
+ Every value is a tagged union, never a bare string, so an unread level can never be reported as an unset one:
173
+
174
+ ```json
175
+ {
176
+ "ok": true,
177
+ "data": {
178
+ "account": { "final_url_suffix": { "state": "set", "value": "utm_source=google" } },
179
+ "campaigns": { "overriding": [], "omitted": 12 },
180
+ "levels_not_read": ["ad_group", "ad"]
181
+ }
182
+ }
183
+ ```
184
+
185
+ `set` holds a value · `not_set` means Google carries none at that level · `not_read` means it was never observed. **Only `not_set` is a finding.**
186
+
187
+ Writing it: `baker ads google campaigns update <id> --final-url-suffix "utm_source=google&utm_agency=baker"` stages a campaign-level override (it *replaces* the account value for that campaign, it does not merge). The **account** level is deliberately not stageable — Google writes it through `CustomerService`, outside the atomic publish batch, so it could not be published atomically nor reversed by discarding the chat. `--level account` stages nothing and returns a structured `error.fix` naming where to set it.
188
+
189
+ ---
190
+
163
191
  ### `baker ads google query`
164
192
 
165
193
  Run arbitrary GAQL queries. The most powerful command.
@@ -191,22 +219,46 @@ baker ads google query --list-presets
191
219
  "ok": true,
192
220
  "data": [
193
221
  { "campaign.name": "Brand US", "metrics.clicks": 4521, "metrics.cost_micros": 2850000000 }
194
- ],
222
+ ]
223
+ }
224
+ ```
225
+
226
+ `--full` adds a `fields` dictionary describing each selected column. It is off by default — the rows already carry the data, and the glossary is a per-call cost.
227
+
228
+ ```json
229
+ {
230
+ "ok": true,
231
+ "data": [...],
195
232
  "fields": {
196
233
  "campaign.name": "Campaign display name",
197
- "metrics.clicks": "Total clicks (integer)",
198
- "metrics.cost_micros": "Total cost in micros (÷ 1,000,000 for actual currency)"
234
+ "metrics.clicks": "Total clicks (integer)"
199
235
  }
200
236
  }
201
237
  ```
202
238
 
239
+ **Absent is not empty.** Google omits a field entirely from a row when it holds no value, so a missing key means *this read did not observe it*, never *the setting is unset*. When one or more selected fields come back on **no** row, the response carries a `MISSING_FIELDS` warning naming them:
240
+
241
+ ```json
242
+ {
243
+ "ok": true,
244
+ "data": [...],
245
+ "warnings": [
246
+ {
247
+ "code": "MISSING_FIELDS",
248
+ "message": "These SELECTed fields came back on no row: ad_group_ad.ad.final_url_suffix. ..."
249
+ }
250
+ ]
251
+ }
252
+ ```
253
+
254
+ Read it before interpreting any null. It does not fire on the `--out` path (rows stream to disk), and `.resource_name` is never reported (the server strips it from every row).
255
+
203
256
  **Response (with pagination):**
204
257
 
205
258
  ```json
206
259
  {
207
260
  "ok": true,
208
261
  "data": [...],
209
- "fields": { ... },
210
262
  "pagination": { "hasMore": true, "cursor": "eyJwYWdl..." }
211
263
  }
212
264
  ```
@@ -823,6 +875,12 @@ baker ads linkedin bid-pricing --account-id 503001492 \
823
875
 
824
876
  **Listings** (`campaign-groups`/`campaigns`/`creatives`) default to `ACTIVE` only — pass `--all-statuses` to widen, or `--statuses ACTIVE,PAUSED` for a custom set.
825
877
 
878
+ **`creatives` is compact by default** — `id`, `urn`, `campaign`, `intendedStatus`, `isServing`, `reviewStatus`, `format`, and the resolved destination. The destination is read out of the format-specific `content` block for you: direct-content ads (text, spotlight) report a `landingUrl`; post-based ads (image, video, carousel, document, article, event, TLA) report a `postUrn`, because their destination lives in the sponsored post and is not reachable through the ads API. Pass `--fields id,content.textAd.landingPage` for specific dotted paths, or `--full` for the complete raw records.
879
+
880
+ **Many ids, one call.** `creatives update` and every `pause`/`resume`/`archive` accept a list — `creatives pause 111,222,333`, or `--ids-file ads.txt` (one id per line or comma-separated, `#` comments skipped). The whole set stages in a single all-or-nothing request: if any id fails validation the draft is left untouched and the error names the offending index, so there is never a half-applied batch to reason about. A single id takes the exact same path as before, including `li_temp_*` amends (amends stay single-id — pass those on their own).
881
+
882
+ **Post-based ads are not editable.** Image, video, carousel, document, article, event and TLA creatives sponsor a LinkedIn post; the copy, media and destination belong to the post, not the ad. Both `creatives update` and `creatives duplicate` reject content flags on these rather than accepting them and silently dropping them. To change the copy, stage a new `creatives create` and pause the old ad. Direct-content formats (text, spotlight, follower, jobs) do update in place.
883
+
826
884
  **Pagination** is auto-drained (start/count loop hidden).
827
885
 
828
886
  **Demographic pivots** (`MEMBER_*`) come back delayed 12-24h with a ≥3-event privacy floor — small buckets are dropped. The CLI surfaces `DELAYED_DEMOGRAPHIC` and (when results are unexpectedly empty) `BELOW_PRIVACY_FLOOR` warnings.