@koda-sl/baker-cli 0.170.0-dev.9ace16483 → 0.170.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
@@ -563,6 +563,13 @@ baker ads google asset-groups create --customer-id 1234567890 --campaign-ref g_t
563
563
  --descriptions "Best widgets around.,Ships tomorrow." --business-name "Acme" --final-urls https://example.com
564
564
  baker ads google asset-groups update g_temp_<assetgroup> --status ENABLED
565
565
 
566
+ # Refresh a LIVE asset group's creatives — attach the replacement first, then detach the old one
567
+ baker ads google assets create --customer-id 1234567890 --file new-image-asset.json # -> g_temp_<img>
568
+ baker ads google asset-groups attach --customer-id 1234567890 \
569
+ --asset-group-ref 6718302454 --asset-ref g_temp_<img> --field-type PORTRAIT_MARKETING_IMAGE
570
+ baker ads google asset-groups detach customers/1234567890/assetGroupAssets/6718302454~402654762568~PORTRAIT_MARKETING_IMAGE \
571
+ --customer-id 1234567890
572
+
566
573
  # Review / undo / amend staged changes
567
574
  baker ads google draft list # readable campaign ▸ ad group ▸ ad tree + completeness advisories
568
575
  baker ads google draft list --json # raw JSON envelope for scripting
@@ -572,7 +579,7 @@ baker ads google draft remove g_temp_ab12 # cascades to dependents
572
579
  baker ads google draft clear
573
580
  ```
574
581
 
575
- Command groups: `budgets`, `campaigns`, `ad-groups`, `keywords` (add/update/remove), `negative-keywords`, `keyword-lists`, `ads`, `assets` (create/update/attach/detach), `asset-groups` (create/update — Performance Max), `audiences`, `conversions` (create/update/goal), `bidding-strategies`, `labels`, `campaign-criteria`, 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 <payload.json>` (flags override file 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**.
582
+ 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`, 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 <payload.json>` (flags override file 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**.
576
583
 
577
584
  **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.
578
585
 
@@ -1047,7 +1054,7 @@ Each finding: `{id, area, check, status, severity, evidence, fix: {explanation,
1047
1054
 
1048
1055
  - OAuth tokens auto-refresh server-side. On 401, reconnect LinkedIn from dashboard → integrations.
1049
1056
  - Account scoping: backend rejects calls against an account not in the picker selection.
1050
- - Pinned API version: `Linkedin-Version: 202604` (declared in `oauth/constants.ts`). Bump as a coordinated change — LinkedIn deprecates monthly with a ~12-month support window.
1057
+ - 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`.
1051
1058
  - 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.
1052
1059
  - `--skip-cache` (server-side) and `--no-cache` (client-side, where supported) on any command.
1053
1060
 
@@ -1159,46 +1166,6 @@ baker ga4 query --dimensions "date,sessionSourceMedium" --metrics "sessions,conv
1159
1166
 
1160
1167
  ---
1161
1168
 
1162
- ### Alarms (`baker alarm`)
1163
-
1164
- How an unattended run reaches a human about something that cannot wait until tomorrow. This is the only thing that interrupts someone out of hours — anything that can wait is `baker actions create --priority urgent`.
1165
-
1166
- Every alarm must carry the evidence that contradicts everything being fine, and the backend **refuses** it when the evidence does not. An all-zeros analytics reading is not evidence on its own: broken tracking, no traffic, a dead page and a failed check all look identical. Paid clicks over the same window are what turn zeros into proof.
1167
-
1168
- ---
1169
-
1170
- ### `baker alarm raise`
1171
-
1172
- Wake a human. Reasons: `tracking_broken`, `spend_anomaly`, `access_lost`, `site_down`.
1173
-
1174
- ```bash
1175
- baker alarm raise --reason tracking_broken --url https://acme.com/demo --window-hours 24 \
1176
- --analytics-answered true --sessions 0 --clicks 47 --spend-micros 82000000
1177
-
1178
- baker alarm raise --reason site_down --url https://acme.com/demo --http-status 503 # 'none' if it never responded
1179
- baker alarm raise --reason spend_anomaly --platform google-ads --window-hours 24 \
1180
- --spend-micros 400000000 --baseline-spend-micros 50000000
1181
- baker alarm raise --reason access_lost --platform google-ads --provider-error "USER_PERMISSION_DENIED"
1182
- ```
1183
-
1184
- `--analytics-answered false` means the provider errored, timed out, or the token expired. That is **not** zero sessions, and the gate refuses it — otherwise a broken check pages somebody about the client's tracking.
1185
-
1186
- Response: `raised: true` (a human was contacted), `deduped: true` (already open — counted, not re-sent), or `raised: false` with a `refusal` explaining what the evidence failed to show. Repeats for an open condition never page twice.
1187
-
1188
- ---
1189
-
1190
- ### `baker alarm checked`
1191
-
1192
- Record that a check came back clean.
1193
-
1194
- ```bash
1195
- baker alarm checked --reason tracking_broken --scope "6 landings"
1196
- ```
1197
-
1198
- Run it whenever a check finds nothing. Without it, "nothing was wrong" and "nobody looked" are the same silence. It never closes an alarm somebody was already woken for — one clean reading does not prove a fix.
1199
-
1200
- ---
1201
-
1202
1169
  ### Google Search Console (`baker gsc`)
1203
1170
 
1204
1171
  GSC commands for PPC-SEO arbitrage, brand halo analysis, and negative keyword discovery.
@@ -4939,6 +4906,10 @@ This CLI is designed for AI agent consumption. Key patterns:
4939
4906
 
4940
4907
  - **0.168.0**: `baker ads google conversions goal` sets the **account-level** conversion goal (`CustomerConversionGoal`) — Google Ads' "Included in account-level goals" column, keyed by `(category, origin)`. `goal --category PURCHASE --origin WEBSITE --biddable` includes a conversion category in what the account bids toward; `--no-biddable` measures it only. This is the half the surface was missing: a conversion drives automated bidding only when BOTH its action is primary AND its category's account-level goal is biddable, which is why an account could show lead-form conversions as Primary and still bid toward nothing. Promoting an action whose goal is off now stages with a warning saying the promotion alone changes no spend, and names the command for the other half. One staged op per pair — a second op for the same `(category, origin)` is refused rather than silently overwriting the first at publish. Switching off the last goal that still has a primary action behind it is refused like a last demotion, and the "no primary left" guard now discounts actions whose goal is already off (they were never steering bidding), while still treating an unread goal as bidding. A goal staged against an account that does not own conversion tracking is refused up front with the id that does, instead of failing the whole atomic batch at publish. The staged card gains "Included in account-level goals", "Conversion category" and "Where it happens" rows.
4941
4908
 
4909
+ - **0.170.0**: `baker ads google asset-groups attach|detach` — link and unlink one asset on a Performance Max asset group that **already exists**, which is what refreshing a live group's creatives actually is. Until now nothing could do it: `asset-groups create` links a group's opening assets inline, `asset-groups update` takes only `--name --final-urls --status`, and `assets attach` reaches campaign/adGroup/customer but not an asset group — so replacing a low-performing PMax image had no route through Baker at all and ended in the Google Ads interface. `create` also gains `--portrait-image-assets`; the 4:5 slot was missing from the asset group contract entirely, so a portrait image could not be staged even at creation. Ordering is the agent's job and is now stated everywhere it matters: operations publish in **stage order**, Google Ads removals carry no dependency edge and are not reordered to run last (`docs/publishing.md` → Known gap), so a detach staged before its replacement reaches Google first and is rejected when it was the last asset of that ratio — attach first, always.
4910
+
4911
+ Shipped with it: **staging a second removal of the same shape silently did nothing.** Amend-matching reduced any composite ref to the segment after its last `~` — built for the two-segment `adGroupId~adId` refs that ad and keyword targets heal to, where that segment really is the entity's own id. It is not, in three other places: an asset-group asset (`{assetGroup}~{asset}~{FIELD_TYPE}`) and a customer-level asset link (`{asset}~{FIELD_TYPE}`) both end in a field type every asset of that ratio shares, and a campaign shared set (`{campaign}~{sharedSet}`) ends in the list's id, the same in every campaign it is attached to. So the second detach amended the first instead of staging, returned the first's ref and reported success: a PMax creative swap could only ever remove one image per ratio, and detaching one negative list from three campaigns removed it from one. Nothing in the output said the rest had been dropped. Refs now compare whole, with one one-sided exception — a **bare** id still collapses onto the two-segment composite whose child it names, which is the only case the reduction ever existed for. The duplicate copy of the same helper in the staging path is gone rather than fixed twice. Also: linking an asset Google created automatically is refused at stage time on an asset group, not just on a campaign/ad-group/customer link — a Performance Max group is where auto-created creative is most likely to be picked up. And the live-context resolver now reads the asset group and asset an attach/detach names, so the review of a swap shows names and the images themselves rather than the ids inside a resource name.
4912
+
4942
4913
  - **0.169.0**: `baker capabilities` — one call, before any platform work, that answers what Baker **cannot** do. Everything else (`baker schema`, the family docs, `--help`) describes what it can, so the only way to learn a capability was absent was to try it, usually after telling the user it would happen. Per surface (`google-ads`, `meta-ads`, `linkedin-ads`, `x-ads`, `tag-manager`, `ga4`, `gsc`) it reports: what is connected and which accounts/properties/containers were picked; whether writes are **live** or **simulated** for this company, and that Tag Manager has neither mode — completing the chat changes the real container; and a `limits` list separating *not built yet* from *the platform forbids it* from *we hold no permission for it*, because each leads to something different to tell the user. Naming one surface (`baker capabilities google-ads`) also returns `ops`: every change with its required and optional fields and the exact values each accepts — so "is there a field to demote a conversion action, and how is it spelled?" is one call instead of four guesses. Almost everything is derived rather than written down: the accounts and permissions come from the connection, the write mode from the company's gate, the writable entities and field contracts from the same schemas the backend parses with, and read-only vs read-write from the OAuth scopes the app actually requests (which is why "GA4 is read-only" is a fact, not a sentence). The handful of hand-written entries each carry a machine-checkable claim about the code behind them, and a coverage test fails when one stops holding. A rejected staged write on Google, Meta or LinkedIn now points at this command instead of leaving the agent to guess another field name.
4943
4914
 
4944
4915
  ## Publishing
@@ -7,9 +7,9 @@ import {
7
7
  __toESM
8
8
  } from "./chunk-RK67WL4O.js";
9
9
 
10
- // ../../.pnpm-store/v10/links/@/safe-stable-stringify/2.5.0/810146e81bae4e3a061fe487864f2fde80c4b03b886877dc0f1fffbc6480b67e/node_modules/safe-stable-stringify/index.js
10
+ // ../../node_modules/.pnpm/safe-stable-stringify@2.5.0/node_modules/safe-stable-stringify/index.js
11
11
  var require_safe_stable_stringify = __commonJS({
12
- "../../.pnpm-store/v10/links/@/safe-stable-stringify/2.5.0/810146e81bae4e3a061fe487864f2fde80c4b03b886877dc0f1fffbc6480b67e/node_modules/safe-stable-stringify/index.js"(exports, module) {
12
+ "../../node_modules/.pnpm/safe-stable-stringify@2.5.0/node_modules/safe-stable-stringify/index.js"(exports, module) {
13
13
  "use strict";
14
14
  var { hasOwnProperty } = Object.prototype;
15
15
  var stringify = configure2();
@@ -1075,7 +1075,7 @@ function resolveAdaptFormats(params) {
1075
1075
  return params.formats ?? [];
1076
1076
  }
1077
1077
 
1078
- // ../../.pnpm-store/v10/links/@/safe-stable-stringify/2.5.0/810146e81bae4e3a061fe487864f2fde80c4b03b886877dc0f1fffbc6480b67e/node_modules/safe-stable-stringify/esm/wrapper.js
1078
+ // ../../node_modules/.pnpm/safe-stable-stringify@2.5.0/node_modules/safe-stable-stringify/esm/wrapper.js
1079
1079
  var import__ = __toESM(require_safe_stable_stringify(), 1);
1080
1080
  var configure = import__.default.configure;
1081
1081
  var wrapper_default = import__.default;
@@ -8028,4 +8028,4 @@ export {
8028
8028
  defaultRegistry,
8029
8029
  createEngineFromEnv
8030
8030
  };
8031
- //# sourceMappingURL=chunk-K424D3CY.js.map
8031
+ //# sourceMappingURL=chunk-CG6OTNUU.js.map