@koda-sl/baker-cli 0.174.0 → 0.177.0-dev.0cbdaa224

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.
Files changed (29) hide show
  1. package/README.md +81 -1
  2. package/dist/{chunk-7I2POXSJ.js → chunk-K3PWXVF7.js} +2 -2
  3. package/dist/{chunk-7QMWNMRW.js → chunk-PXXJ3HJW.js} +4 -4
  4. package/dist/{chunk-CG6OTNUU.js → chunk-QPVJGKV7.js} +6 -6
  5. package/dist/chunk-QPVJGKV7.js.map +1 -0
  6. package/dist/{chunk-TFPZLUVM.js → chunk-X5C6HE24.js} +5 -5
  7. package/dist/chunk-X5C6HE24.js.map +1 -0
  8. package/dist/{chunk-RK67WL4O.js → chunk-YL3HDEIJ.js} +6 -2
  9. package/dist/{chunk-RK67WL4O.js.map → chunk-YL3HDEIJ.js.map} +1 -1
  10. package/dist/{chunk-ZH6ZNYPB.js → chunk-ZWYQIEBI.js} +3 -3
  11. package/dist/cli.js +1643 -343
  12. package/dist/cli.js.map +1 -1
  13. package/dist/client-PJ7ID35L.js +15 -0
  14. package/dist/engine/index.js +3 -3
  15. package/dist/env-6QJCMTRK.js +13 -0
  16. package/dist/{output-FL7WRIJF.js → output-NWX3YW64.js} +5 -5
  17. package/dist/{shared-OIYLDW3G.js → shared-5ZEOG664.js} +6 -6
  18. package/package.json +1 -1
  19. package/dist/chunk-CG6OTNUU.js.map +0 -1
  20. package/dist/chunk-TFPZLUVM.js.map +0 -1
  21. package/dist/client-XRQWOADV.js +0 -15
  22. package/dist/env-3JMYIH25.js +0 -11
  23. /package/dist/{chunk-7I2POXSJ.js.map → chunk-K3PWXVF7.js.map} +0 -0
  24. /package/dist/{chunk-7QMWNMRW.js.map → chunk-PXXJ3HJW.js.map} +0 -0
  25. /package/dist/{chunk-ZH6ZNYPB.js.map → chunk-ZWYQIEBI.js.map} +0 -0
  26. /package/dist/{client-XRQWOADV.js.map → client-PJ7ID35L.js.map} +0 -0
  27. /package/dist/{env-3JMYIH25.js.map → env-6QJCMTRK.js.map} +0 -0
  28. /package/dist/{output-FL7WRIJF.js.map → output-NWX3YW64.js.map} +0 -0
  29. /package/dist/{shared-OIYLDW3G.js.map → shared-5ZEOG664.js.map} +0 -0
package/README.md CHANGED
@@ -577,6 +577,14 @@ baker ads google draft show g_temp_ab12 # full staged payload — the recei
577
577
  baker ads google draft amend g_temp_ab12 --patch '{"amountMicros": 60000000}' # update in place, never remove+recreate
578
578
  baker ads google draft remove g_temp_ab12 # cascades to dependents
579
579
  baker ads google draft clear
580
+
581
+ # Read an earlier chat's changes (read-only; staging stays on the current chat)
582
+ baker ads google draft list --chat <chat-id>
583
+ baker ads google draft show --chat <chat-id> g_temp_ab12
584
+ baker ads meta draft show --chat <chat-id> meta_temp_ab12
585
+ baker ads linkedin draft show --chat <chat-id> li_temp_ab12
586
+ baker tag-manager draft show --chat <chat-id> gtm_temp_ab12
587
+ baker actions draft --chat <chat-id>
580
588
  ```
581
589
 
582
590
  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**.
@@ -591,6 +599,8 @@ Command groups: `budgets`, `campaigns`, `ad-groups`, `keywords` (add/update/remo
591
599
 
592
600
  **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.
593
601
 
602
+ **Reading an earlier chat's changes** — `draft list` and `draft show` take `--chat <chat-id>` to read another chat in the same company instead of the current one. Every staged-draft family accepts it: `ads google|meta|linkedin draft list|show`, `tag-manager draft list|show`, `actions draft`, `tags draft`, and `scheduled-actions list|get`. A published draft is kept in full, so this returns what that chat actually wrote — every headline, bid and target — plus each op's outcome and error. Use it when asked to redo or reapply earlier work rather than reconstructing copy from a summary. Read-only: staging, `amend`, `remove` and `clear` stay bound to `BAKER_CHAT_ID`, so one chat can never edit another's draft. To re-run only the changes a publish failed to land, use the chat's **Retry** control in the dashboard — it re-sends exactly those and leaves the ones that applied untouched.
603
+
594
604
  **Already-satisfied writes aren't staged** — an update or pause/resume whose every field already holds the requested value on the live account is dropped instead of staged: the envelope returns `"staged": false, "noop": true` with a `reason` ("this ad is already Paused on Google Ads"), plus a hint. This is a success, not an error — there is nothing to publish. Only a positively-read live snapshot can trigger it; a failed or empty read always stages. Batch responses list the dropped ops under `skipped`, and `count` reflects what actually entered the draft.
595
605
 
596
606
  **Batch keyword adds** — `keywords add`, `negative-keywords add`, and `keyword-lists add` take a whole batch in one command: comma-separate `--text` entries and/or pass `--file <list.txt>` (one keyword per line). A `:EXACT`/`:PHRASE`/`:BROAD` suffix per entry overrides the `--match-type` default. Batches stage all-or-nothing as one request (limit 500); each keyword still lands as its own draft op, so it stays individually removable/amendable.
@@ -786,6 +796,7 @@ baker ads meta ads create --account-id act_123 --name "Ad 1" --adset met
786
796
 
787
797
  # review + edit before publish
788
798
  baker ads meta draft
799
+ baker ads meta draft show meta_temp_cr # full staged payload — the receipt before publish
789
800
  baker ads meta draft remove meta_temp_cr # removing a create cascades to dependents
790
801
  baker ads meta draft clear
791
802
  ```
@@ -1090,7 +1101,11 @@ Each finding: `{id, area, check, status, severity, evidence, fix: {explanation,
1090
1101
 
1091
1102
  ### Google Analytics 4 (`baker ga4`)
1092
1103
 
1093
- GA4 commands for multi-channel audits. Playbook-aligned presets, property health audits, and free-form Data API queries.
1104
+ GA4 commands for multi-channel audits **and** for configuring what the property measures. Playbook-aligned report presets, property health audits, free-form Data API queries, and staged Admin API writes.
1105
+
1106
+ Reads (`properties`, `config`, `audit`, `query`) happen immediately. Writes (`key-event`, `custom-dimension`, `custom-metric`, `custom-event`, `data-retention`) are **staged** onto the chat, reviewed in the dashboard's Analytics tab, and applied when the chat completes — at which point they take effect on the property immediately. There is no simulated mode and no undo.
1107
+
1108
+ A Google Analytics connection made before writes shipped is read-only and every write command refuses with a message saying to reconnect. `baker ga4 config` reports `canWrite`.
1094
1109
 
1095
1110
  ---
1096
1111
 
@@ -1104,6 +1119,18 @@ baker ga4 properties
1104
1119
 
1105
1120
  ---
1106
1121
 
1122
+ ### `baker ga4 config`
1123
+
1124
+ What the property measures today — key events, custom dimensions and metrics, custom events, data streams, retention — plus whether this connection may change it. Run before staging any write: every id an update or removal targets comes from here.
1125
+
1126
+ ```bash
1127
+ baker ga4 config
1128
+ baker ga4 config --property-id 123456789
1129
+ baker ga4 config --full
1130
+ ```
1131
+
1132
+ ---
1133
+
1107
1134
  ### `baker ga4 audit`
1108
1135
 
1109
1136
  Run all GA4 admin health checks at once. Checks data retention, Google Ads linkage, phantom conversions, audience defaults, data streams, and attribution settings.
@@ -1172,6 +1199,58 @@ baker ga4 query --dimensions "date,sessionSourceMedium" --metrics "sessions,conv
1172
1199
 
1173
1200
  ---
1174
1201
 
1202
+ ### `baker ga4 key-event` / `custom-dimension` / `custom-metric` / `custom-event` / `data-retention`
1203
+
1204
+ Staged Admin API writes. Each stages one change onto the chat; nothing reaches Google Analytics until the chat completes.
1205
+
1206
+ ```bash
1207
+ # What GA4 counts as a conversion (and Google Ads imports for bidding)
1208
+ baker ga4 key-event create --json '{"eventName":"generate_lead","countingMethod":"ONCE_PER_EVENT"}'
1209
+ baker ga4 key-event update 4185 --json '{"countingMethod":"ONCE_PER_SESSION"}'
1210
+ baker ga4 key-event delete 4185
1211
+
1212
+ # Make an event parameter reportable
1213
+ baker ga4 custom-dimension create --json '{"parameterName":"plan_tier","displayName":"Plan tier","scope":"EVENT"}'
1214
+ baker ga4 custom-dimension update 12 --json '{"displayName":"Plan"}'
1215
+ baker ga4 custom-dimension archive 12
1216
+ baker ga4 custom-metric create --json '{"parameterName":"deal_value","displayName":"Deal value","scope":"EVENT","measurementUnit":"CURRENCY","restrictedMetricType":["REVENUE_DATA"]}'
1217
+ baker ga4 custom-metric update 8 --json '{"description":"Closed-won value"}'
1218
+ baker ga4 custom-metric archive 8
1219
+
1220
+ # A new event built from one the site already sends (GA4's "Create event")
1221
+ baker ga4 custom-event create --json '{"destinationEvent":"quote_request","eventConditions":[{"field":"event_name","comparisonType":"EQUALS","value":"form_submit"},{"field":"page_location","comparisonType":"CONTAINS","value":"/quote"}],"sourceCopyParameters":true}'
1222
+ baker ga4 custom-event update 7 --json '{"sourceCopyParameters":false}'
1223
+ baker ga4 custom-event delete 7
1224
+
1225
+ # How long data is kept (months: 2, 14, 26, 38, 50 — over 14 needs Analytics 360)
1226
+ baker ga4 data-retention set --event-data 14 --user-data 14
1227
+
1228
+ # Several definitions in one call — one read of the property instead of one per definition
1229
+ baker ga4 custom-dimension create --json '[{"parameterName":"plan_tier","displayName":"Plan tier","scope":"EVENT"},{"parameterName":"lead_source","displayName":"Lead source","scope":"EVENT"}]'
1230
+ ```
1231
+
1232
+ Every `create` accepts a JSON array as well as a single object, and stages the whole list in one request (all-or-nothing). Prefer it for bulk work: each stage reads the property's full configuration first, so separate calls multiply that read.
1233
+
1234
+ Custom dimensions and metrics are **archived**, not deleted (the slot frees, past data stays); key events and custom events are **deleted**. Neither is reversible from Baker. Because `parameterName` and `scope` are immutable, changing one is an archive plus a create staged on the same chat — the removal applies first and frees the name. `--property-id` is required when more than one property is connected, and one chat stages changes for one property; `--data-stream` is required for a custom event when the property has more than one website stream.
1235
+
1236
+ ---
1237
+
1238
+ ### `baker ga4 draft`
1239
+
1240
+ Review and undo the Analytics changes staged on this chat.
1241
+
1242
+ ```bash
1243
+ baker ga4 draft list # everything staged (--json for the envelope)
1244
+ baker ga4 draft show ga4_temp_keyEvent_x_0 # one change in full, with today's values
1245
+ baker ga4 draft amend keyEvent:4185 --patch '{"countingMethod":"ONCE_PER_EVENT"}'
1246
+ baker ga4 draft remove keyEvent:4185
1247
+ baker ga4 draft clear
1248
+ ```
1249
+
1250
+ `list` and `show` take `--chat <id>` to read an earlier chat's staged changes.
1251
+
1252
+ ---
1253
+
1175
1254
  ### Google Search Console (`baker gsc`)
1176
1255
 
1177
1256
  GSC commands for PPC-SEO arbitrage, brand halo analysis, and negative keyword discovery.
@@ -4930,6 +5009,7 @@ This CLI is designed for AI agent consumption. Key patterns:
4930
5009
 
4931
5010
  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.
4932
5011
 
5012
+ - **0.177.0**: `baker ga4` can change what a property measures, not only report on it. Five staged write families — `key-event` (create|update|delete), `custom-dimension` and `custom-metric` (create|update|archive), `custom-event` (create|update|delete), `data-retention set` — plus `baker ga4 config` to read the property's current configuration and `baker ga4 draft` (list|show|amend|remove|clear) to review the staged set. `custom-event` is GA4's "Create event" rule: build a new event out of one the site already sends (a `form_submit` on `/quote` becomes `quote_request`), then mark it a key event so Google Ads imports it — the loop that previously ended with "do this by hand in Analytics". Staging follows Tag Manager: nothing is sent during the chat, the user reviews the whole set in the dashboard's **Analytics** tab, and it applies when they complete the chat. Unlike Tag Manager there is no version left over — an applied change is in effect at once, and there is no undo, so removals ask for confirmation first. Everything Google would reject is refused at stage time against a fresh read of the property rather than discovered at publish: a duplicate `eventName` or `parameterName` (including against another change staged on the same chat), an immutable field in an update, a currency metric with no restricted type, a retention window past 14 months on a standard property, a target the property does not have. Marking an event Google fires by itself (`page_view`, `scroll`, …) as a key event is a **warning**, not a refusal — it is legal, it just inflates conversions and poisons Smart Bidding. The one thing that is *not* a duplicate is the replacement of something the same chat has already staged the removal of: ops apply in stage order, so `custom-dimension archive 12` followed by a create on the same `parameterName` works, which is the only way to change a field GA4 marks immutable. Every `create` also takes a JSON array and stages the list in one request, because each stage costs a full read of the property. One chat stages changes for one property. **The OAuth scope changed:** Baker now requests `analytics.edit`, so every Google Analytics connection made before this is read-only until it is reconnected; the write commands refuse up front with that as the fix, and `baker ga4 config` reports `canWrite`. Reporting is unaffected. Not covered, and reported as such by `baker capabilities`: property create/rename/merge, data streams, the Google Ads link, and audiences.
4933
5013
  - **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.
4934
5014
 
4935
5015
  ## Publishing
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  debugLogSetting
3
- } from "./chunk-RK67WL4O.js";
3
+ } from "./chunk-YL3HDEIJ.js";
4
4
 
5
5
  // src/debugLog.ts
6
6
  import { appendFileSync, mkdirSync, renameSync, statSync } from "fs";
@@ -150,4 +150,4 @@ export {
150
150
  readBodyForLog,
151
151
  installStreamTaps
152
152
  };
153
- //# sourceMappingURL=chunk-7I2POXSJ.js.map
153
+ //# sourceMappingURL=chunk-K3PWXVF7.js.map
@@ -2,13 +2,13 @@ import {
2
2
  handleConnectionError,
3
3
  needsConnectionFix,
4
4
  writeAdsJson
5
- } from "./chunk-TFPZLUVM.js";
5
+ } from "./chunk-X5C6HE24.js";
6
6
  import {
7
7
  ApiError
8
- } from "./chunk-ZH6ZNYPB.js";
8
+ } from "./chunk-ZWYQIEBI.js";
9
9
  import {
10
10
  getEnv
11
- } from "./chunk-RK67WL4O.js";
11
+ } from "./chunk-YL3HDEIJ.js";
12
12
 
13
13
  // src/commands/ads/meta/shared.ts
14
14
  var DAY_MS = 864e5;
@@ -108,4 +108,4 @@ export {
108
108
  csvOrJson,
109
109
  resolveEffectiveStatus
110
110
  };
111
- //# sourceMappingURL=chunk-7QMWNMRW.js.map
111
+ //# sourceMappingURL=chunk-PXXJ3HJW.js.map
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  debugLogHttp,
3
3
  readBodyForLog
4
- } from "./chunk-7I2POXSJ.js";
4
+ } from "./chunk-K3PWXVF7.js";
5
5
  import {
6
6
  __commonJS,
7
7
  __toESM
8
- } from "./chunk-RK67WL4O.js";
8
+ } from "./chunk-YL3HDEIJ.js";
9
9
 
10
- // ../../node_modules/.pnpm/safe-stable-stringify@2.5.0/node_modules/safe-stable-stringify/index.js
10
+ // ../../.pnpm-store/v10/links/@/safe-stable-stringify/2.5.0/810146e81bae4e3a061fe487864f2fde80c4b03b886877dc0f1fffbc6480b67e/node_modules/safe-stable-stringify/index.js
11
11
  var require_safe_stable_stringify = __commonJS({
12
- "../../node_modules/.pnpm/safe-stable-stringify@2.5.0/node_modules/safe-stable-stringify/index.js"(exports, module) {
12
+ "../../.pnpm-store/v10/links/@/safe-stable-stringify/2.5.0/810146e81bae4e3a061fe487864f2fde80c4b03b886877dc0f1fffbc6480b67e/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
- // ../../node_modules/.pnpm/safe-stable-stringify@2.5.0/node_modules/safe-stable-stringify/esm/wrapper.js
1078
+ // ../../.pnpm-store/v10/links/@/safe-stable-stringify/2.5.0/810146e81bae4e3a061fe487864f2fde80c4b03b886877dc0f1fffbc6480b67e/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-CG6OTNUU.js.map
8031
+ //# sourceMappingURL=chunk-QPVJGKV7.js.map