@koda-sl/baker-cli 0.233.0 → 0.234.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
@@ -1155,7 +1155,7 @@ Each finding: `{id, area, check, status, severity, evidence, fix: {explanation,
1155
1155
 
1156
1156
  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.
1157
1157
 
1158
- 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.
1158
+ Reads (`properties`, `config`, `audit`, `query`) happen immediately. Writes (`key-event`, `custom-dimension`, `custom-metric`, `custom-event`, `data-retention`, `audience`, `property`, `ads-link`, `enhanced-measurement`) 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.
1159
1159
 
1160
1160
  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`.
1161
1161
 
@@ -1277,12 +1277,31 @@ baker ga4 custom-event delete 7
1277
1277
  # How long data is kept (months: 2, 14, 26, 38, 50 — over 14 needs Analytics 360)
1278
1278
  baker ga4 data-retention set --event-data 14 --user-data 14
1279
1279
 
1280
+ # An audience — a saved group of users, for retargeting and analysis
1281
+ baker ga4 audience create --json '{"displayName":"Started demo without finishing","description":"Triggered demo_started and never submitted the demo form","membershipDurationDays":90,"filterClauses":[{"clauseType":"INCLUDE","simpleFilter":{"scope":"AUDIENCE_FILTER_SCOPE_ACROSS_ALL_SESSIONS","filterExpression":{"eventFilter":{"eventName":"demo_started"}}}},{"clauseType":"EXCLUDE","simpleFilter":{"scope":"AUDIENCE_FILTER_SCOPE_ACROSS_ALL_SESSIONS","filterExpression":{"eventFilter":{"eventName":"demo_form_submitted"}}}}]}'
1282
+ baker ga4 audience update 8813 --json '{"displayName":"Started demo without finishing (90d)"}'
1283
+ baker ga4 audience archive 8813
1284
+
1285
+ # The property's own settings — the industry decides its benchmarking cohort
1286
+ baker ga4 property set --industry TECHNOLOGY
1287
+
1288
+ # The automatic web-stream events — no tag to add
1289
+ baker ga4 enhanced-measurement set --scrolls true --outbound-clicks true --file-downloads true
1290
+ baker ga4 enhanced-measurement set --site-search true --search-parameters "q,s,search,query,keyword"
1291
+
1292
+ # Link to Google Ads — what makes audiences targetable and key events importable
1293
+ baker ga4 ads-link create --customer-id 123-456-7890
1294
+ baker ga4 ads-link update L1234 --ads-personalization false
1295
+ baker ga4 ads-link delete L1234
1296
+
1280
1297
  # Several definitions in one call — one read of the property instead of one per definition
1281
1298
  baker ga4 custom-dimension create --json '[{"parameterName":"plan_tier","displayName":"Plan tier","scope":"EVENT"},{"parameterName":"lead_source","displayName":"Lead source","scope":"EVENT"}]'
1282
1299
  ```
1283
1300
 
1284
1301
  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.
1285
1302
 
1303
+ An **audience is permanent once created**: its conditions, membership window and exclusion mode are immutable, so `audience update` takes only a name and a description, and fixing a definition means archiving it and building a replacement that starts with no users. Staging also warns when the property has no Google Ads link, because an audience without one can never be retargeted. `enhanced-measurement` sets the events GA4 collects on a website stream by itself; `--site-search true` requires `--search-parameters`, and staging refuses without them rather than letting the publish fail. `ads-link` is the only GA4 command that reaches outside Analytics — it starts a flow of the client's data between their Analytics property and their Google Ads account, so staging warns and the agent is told to agree it with the user first. `property set` covers the display name and the industry category only — time zone and currency are excluded on purpose, since both re-bucket every future day without reprocessing history. Sequence-based audiences are not supported.
1304
+
1286
1305
  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.
1287
1306
 
1288
1307
  ---
package/dist/cli.js CHANGED
@@ -32003,10 +32003,14 @@ async function draftList(json, chat) {
32003
32003
  // src/commands/ga4/config.ts
32004
32004
  registerSchema({
32005
32005
  command: "ga4.config",
32006
- description: "Read how a Google Analytics property is configured to measure: key events (conversions), custom dimensions and metrics, custom events, data streams and the data retention window. Start here before staging any change \u2014 every id an update or removal targets comes from this, and `canWrite` says whether this connection may change anything at all. Compact by default; pass --full for the raw Analytics objects.",
32006
+ description: "Read how a Google Analytics property is configured to measure: key events (conversions), custom dimensions and metrics, custom events, audiences, linked Google Ads accounts, data streams and the data retention window. Start here before staging any change \u2014 every id an update or removal targets comes from this, and `canWrite` says whether this connection may change anything at all. Compact by default; pass --full for the raw Analytics objects AND each website stream's enhanced measurement settings (the automatic events), which are the only way to see what is on before changing a toggle.",
32007
32007
  args: {
32008
32008
  "property-id": { type: "string", description: "GA4 property id (optional when one is connected)", required: false },
32009
- full: { type: "boolean", description: "Include the raw Analytics object for each entity", required: false }
32009
+ full: {
32010
+ type: "boolean",
32011
+ description: "Include the raw Analytics object for each entity, plus each website stream's enhanced measurement settings",
32012
+ required: false
32013
+ }
32010
32014
  }
32011
32015
  });
32012
32016
  function configHints(config) {
@@ -32039,11 +32043,15 @@ var configCommand = defineCommand116({
32039
32043
  Examples:
32040
32044
  baker ga4 config
32041
32045
  baker ga4 config --property-id 123456789
32042
- baker ga4 config --full`
32046
+ baker ga4 config --full # + the automatic events each website stream collects today`
32043
32047
  },
32044
32048
  args: {
32045
32049
  "property-id": { type: "string", description: "GA4 property id", required: false },
32046
- full: { type: "boolean", description: "Include raw Analytics objects", required: false }
32050
+ full: {
32051
+ type: "boolean",
32052
+ description: "Include raw Analytics objects and enhanced measurement settings",
32053
+ required: false
32054
+ }
32047
32055
  },
32048
32056
  run: async ({ args }) => {
32049
32057
  try {
@@ -32769,6 +32777,327 @@ Examples:
32769
32777
  })
32770
32778
  }
32771
32779
  });
32780
+ registerSchema({
32781
+ command: "ga4.audience",
32782
+ description: "Create an audience (a saved group of users, e.g. everyone who started a demo but never finished), rename one, or archive one. Read `baker ga4 config` first to see the audiences that already exist. IMPORTANT: everything that defines who is in an audience \u2014 the conditions, the membership window \u2014 is frozen the moment it is created and cannot be edited afterwards, so get it right in one go. An audience only reaches Google Ads for retargeting if the property is linked to a Google Ads account.",
32783
+ args: {
32784
+ json: { type: "string", description: createJsonDescription("audience"), required: false },
32785
+ file: { type: "string", description: "Path to a JSON file with the definition", required: false },
32786
+ "property-id": { type: "string", description: PROPERTY_ARG_DESCRIPTION, required: false }
32787
+ }
32788
+ });
32789
+ var audienceCommand = defineCommand120({
32790
+ meta: {
32791
+ name: "audience",
32792
+ description: `Stage audience changes (saved groups of users, for retargeting and analysis)
32793
+
32794
+ The definition is PERMANENT: conditions and membership window cannot be edited after creation.
32795
+ Fixing one means archiving it and building a replacement, which does not carry its users over.
32796
+
32797
+ Examples:
32798
+ # Everyone who visited but never submitted the demo form, kept for 90 days
32799
+ baker ga4 audience create --json '{
32800
+ "displayName":"Visited without demo",
32801
+ "description":"Reached the site, never submitted the demo form",
32802
+ "membershipDurationDays":90,
32803
+ "filterClauses":[
32804
+ {"clauseType":"INCLUDE","simpleFilter":{"scope":"AUDIENCE_FILTER_SCOPE_ACROSS_ALL_SESSIONS",
32805
+ "filterExpression":{"eventFilter":{"eventName":"page_view"}}}},
32806
+ {"clauseType":"EXCLUDE","simpleFilter":{"scope":"AUDIENCE_FILTER_SCOPE_ACROSS_ALL_SESSIONS",
32807
+ "filterExpression":{"eventFilter":{"eventName":"demo_form_submitted"}}}}
32808
+ ]}'
32809
+
32810
+ # Everyone who landed on a pricing page
32811
+ baker ga4 audience create --json '{"displayName":"Saw pricing","description":"Viewed a pricing page",
32812
+ "membershipDurationDays":60,
32813
+ "filterClauses":[{"clauseType":"INCLUDE","simpleFilter":{"scope":"AUDIENCE_FILTER_SCOPE_ACROSS_ALL_SESSIONS",
32814
+ "filterExpression":{"dimensionOrMetricFilter":{"fieldName":"pagePath",
32815
+ "stringFilter":{"matchType":"CONTAINS","value":"/pricing"}}}}}]}'
32816
+
32817
+ baker ga4 audience update 8813 --json '{"displayName":"Visited without demo (90d)"}' \u2014 name/description only
32818
+ baker ga4 audience archive 8813`
32819
+ },
32820
+ subCommands: {
32821
+ create: defineCommand120({
32822
+ meta: { name: "create", description: createJsonDescription("audience") },
32823
+ args: {
32824
+ json: { type: "string", description: createJsonDescription("audience"), required: false },
32825
+ file: { type: "string", description: "Path to a JSON file", required: false },
32826
+ "property-id": propertyArg
32827
+ },
32828
+ run: async ({ args }) => {
32829
+ const payloads = loadJsonPayloads(args);
32830
+ await stageOps(
32831
+ payloads.map((payload) => ({
32832
+ kind: "ga4.audience.create",
32833
+ payload,
32834
+ ...withProperty(args)
32835
+ }))
32836
+ );
32837
+ }
32838
+ }),
32839
+ update: defineCommand120({
32840
+ meta: {
32841
+ name: "update",
32842
+ description: "Stage a new name or description (pass the audience id). Who is in it cannot be changed."
32843
+ },
32844
+ args: {
32845
+ json: { type: "string", description: 'Inline JSON, e.g. {"displayName":"\u2026"}', required: false },
32846
+ file: { type: "string", description: "Path to a JSON file", required: false },
32847
+ "property-id": propertyArg
32848
+ },
32849
+ run: async ({ args }) => {
32850
+ const target = requireTarget4(args, "audience id");
32851
+ const payload = loadJsonArg(args);
32852
+ await stageOp3({
32853
+ kind: "ga4.audience.update",
32854
+ target,
32855
+ payload,
32856
+ ...withProperty(args)
32857
+ });
32858
+ }
32859
+ }),
32860
+ archive: defineCommand120({
32861
+ meta: {
32862
+ name: "archive",
32863
+ description: "Stage retiring an audience. Cannot be undone \u2014 the users it gathered do not come back."
32864
+ },
32865
+ args: { "property-id": propertyArg },
32866
+ run: async ({ args }) => {
32867
+ await stageOp3({
32868
+ kind: "ga4.audience.archive",
32869
+ target: requireTarget4(args, "audience id"),
32870
+ ...withProperty(args)
32871
+ });
32872
+ }
32873
+ })
32874
+ }
32875
+ });
32876
+ registerSchema({
32877
+ command: "ga4.property",
32878
+ description: "Set the property's name or industry category. The industry decides which businesses Google benchmarks this property against, so a property left on OTHER is being compared to nothing useful. Time zone and currency are deliberately not changeable here \u2014 both re-bucket every future day without reprocessing history, so they belong to a person who knows the reporting calendar.",
32879
+ args: {
32880
+ name: { type: "string", description: "New property display name", required: false },
32881
+ industry: {
32882
+ type: "string",
32883
+ description: "Industry category, e.g. TECHNOLOGY, FINANCE, HEALTHCARE, REAL_ESTATE, JOBS_AND_EDUCATION",
32884
+ required: false
32885
+ },
32886
+ "property-id": { type: "string", description: PROPERTY_ARG_DESCRIPTION, required: false }
32887
+ }
32888
+ });
32889
+ var propertyCommand = defineCommand120({
32890
+ meta: {
32891
+ name: "property",
32892
+ description: `Stage a change to the property's own settings
32893
+
32894
+ Examples:
32895
+ baker ga4 property set --industry TECHNOLOGY
32896
+ baker ga4 property set --name "Acme \u2014 Web" --industry REAL_ESTATE`
32897
+ },
32898
+ subCommands: {
32899
+ set: defineCommand120({
32900
+ meta: { name: "set", description: "Stage the property name and/or industry category" },
32901
+ args: {
32902
+ name: { type: "string", description: "New property display name", required: false },
32903
+ industry: { type: "string", description: "Industry category, e.g. TECHNOLOGY", required: false },
32904
+ "property-id": propertyArg
32905
+ },
32906
+ run: async ({ args }) => {
32907
+ const payload = {};
32908
+ if (typeof args.name === "string" && args.name.length > 0) {
32909
+ payload.displayName = args.name;
32910
+ }
32911
+ if (typeof args.industry === "string" && args.industry.length > 0) {
32912
+ payload.industryCategory = args.industry.trim().toUpperCase();
32913
+ }
32914
+ if (Object.keys(payload).length === 0) {
32915
+ failValidation3("pass --name, --industry, or both");
32916
+ }
32917
+ await stageOp3({ kind: "ga4.property.update", payload, ...withProperty(args) });
32918
+ }
32919
+ })
32920
+ }
32921
+ });
32922
+ registerSchema({
32923
+ command: "ga4.adsLink",
32924
+ description: "Link this Analytics property to a Google Ads account, or unlink it. This is what lets Google Ads import the property's key events for bidding and makes audiences built here targetable \u2014 `baker ga4 audit` reports the gap as NO_ADS_LINK. It reaches outside Analytics and moves the client's data between two products, so agree it with the user before staging it.",
32925
+ args: {
32926
+ "customer-id": {
32927
+ type: "string",
32928
+ description: "Google Ads customer id, with or without dashes (123-456-7890)",
32929
+ required: false
32930
+ },
32931
+ "ads-personalization": {
32932
+ type: "string",
32933
+ description: "true|false \u2014 may this property's audiences be used for ads personalisation",
32934
+ required: false
32935
+ },
32936
+ "property-id": { type: "string", description: PROPERTY_ARG_DESCRIPTION, required: false }
32937
+ }
32938
+ });
32939
+ var adsLinkCommand = defineCommand120({
32940
+ meta: {
32941
+ name: "ads-link",
32942
+ description: `Stage linking this property to a Google Ads account
32943
+
32944
+ Without this link, Google Ads imports no key events from the property and audiences built
32945
+ here cannot be retargeted. It moves the client's data between two products \u2014 agree it first.
32946
+
32947
+ Examples:
32948
+ baker ga4 ads-link create --customer-id 123-456-7890
32949
+ baker ga4 ads-link create --customer-id 1234567890 --ads-personalization true
32950
+ baker ga4 ads-link update L1234 --ads-personalization false
32951
+ baker ga4 ads-link delete L1234`
32952
+ },
32953
+ subCommands: {
32954
+ create: defineCommand120({
32955
+ meta: { name: "create", description: "Stage linking the property to a Google Ads account" },
32956
+ args: {
32957
+ "customer-id": { type: "string", description: "Google Ads customer id (123-456-7890)", required: false },
32958
+ "ads-personalization": { type: "string", description: "true|false", required: false },
32959
+ "property-id": propertyArg
32960
+ },
32961
+ run: async ({ args }) => {
32962
+ const customerId = args["customer-id"];
32963
+ if (typeof customerId !== "string" || customerId.length === 0) {
32964
+ failValidation3("pass --customer-id, the Google Ads account to link (123-456-7890)");
32965
+ }
32966
+ const payload = { customerId };
32967
+ if (typeof args["ads-personalization"] === "string") {
32968
+ payload.adsPersonalizationEnabled = booleanArg(args["ads-personalization"], "ads-personalization");
32969
+ }
32970
+ await stageOp3({ kind: "ga4.googleAdsLink.create", payload, ...withProperty(args) });
32971
+ }
32972
+ }),
32973
+ update: defineCommand120({
32974
+ meta: {
32975
+ name: "update",
32976
+ description: "Stage the ads-personalisation setting on an existing link (pass its id)"
32977
+ },
32978
+ args: {
32979
+ "ads-personalization": { type: "string", description: "true|false", required: false },
32980
+ "property-id": propertyArg
32981
+ },
32982
+ run: async ({ args }) => {
32983
+ const target = requireTarget4(args, "Google Ads link id");
32984
+ if (typeof args["ads-personalization"] !== "string") {
32985
+ failValidation3("pass --ads-personalization true|false \u2014 it is the only thing a link update can change");
32986
+ }
32987
+ await stageOp3({
32988
+ kind: "ga4.googleAdsLink.update",
32989
+ target,
32990
+ payload: { adsPersonalizationEnabled: booleanArg(args["ads-personalization"], "ads-personalization") },
32991
+ ...withProperty(args)
32992
+ });
32993
+ }
32994
+ }),
32995
+ delete: defineCommand120({
32996
+ meta: {
32997
+ name: "delete",
32998
+ description: "Stage unlinking. Stops conversion imports and strands every audience on this property."
32999
+ },
33000
+ args: { "property-id": propertyArg },
33001
+ run: async ({ args }) => {
33002
+ await stageOp3({
33003
+ kind: "ga4.googleAdsLink.delete",
33004
+ target: requireTarget4(args, "Google Ads link id"),
33005
+ ...withProperty(args)
33006
+ });
33007
+ }
33008
+ })
33009
+ }
33010
+ });
33011
+ registerSchema({
33012
+ command: "ga4.enhancedMeasurement",
33013
+ description: "Turn the automatic web-stream events on or off: scrolls, outbound clicks, site search, video engagement, file downloads, page changes (single-page apps), form interactions. These are collected by Google Analytics itself with no tag to add, so this is usually the cheapest measurement win available. Run `baker ga4 config --full` to see what is on today.",
33014
+ args: {
33015
+ scrolls: { type: "string", description: "true|false", required: false },
33016
+ "outbound-clicks": { type: "string", description: "true|false", required: false },
33017
+ "site-search": { type: "string", description: "true|false \u2014 needs --search-parameters", required: false },
33018
+ "search-parameters": {
33019
+ type: "string",
33020
+ description: "URL parameters carrying the search term, e.g. q,s,search,query,keyword",
33021
+ required: false
33022
+ },
33023
+ video: { type: "string", description: "true|false", required: false },
33024
+ "file-downloads": { type: "string", description: "true|false", required: false },
33025
+ "page-changes": { type: "string", description: "true|false \u2014 page views in a single-page app", required: false },
33026
+ "form-interactions": { type: "string", description: "true|false", required: false },
33027
+ enabled: { type: "string", description: "true|false \u2014 the master switch for the whole feature", required: false },
33028
+ "data-stream": {
33029
+ type: "string",
33030
+ description: "Only when the property has several website streams",
33031
+ required: false
33032
+ },
33033
+ "property-id": { type: "string", description: PROPERTY_ARG_DESCRIPTION, required: false }
33034
+ }
33035
+ });
33036
+ var MEASUREMENT_FLAGS = {
33037
+ enabled: "streamEnabled",
33038
+ scrolls: "scrollsEnabled",
33039
+ "outbound-clicks": "outboundClicksEnabled",
33040
+ "site-search": "siteSearchEnabled",
33041
+ video: "videoEngagementEnabled",
33042
+ "file-downloads": "fileDownloadsEnabled",
33043
+ "page-changes": "pageChangesEnabled",
33044
+ "form-interactions": "formInteractionsEnabled"
33045
+ };
33046
+ var enhancedMeasurementCommand = defineCommand120({
33047
+ meta: {
33048
+ name: "enhanced-measurement",
33049
+ description: `Stage the automatic events GA4 collects on a website stream
33050
+
33051
+ No tag to add \u2014 Google Analytics collects these itself. Usually the cheapest measurement win there is.
33052
+
33053
+ Examples:
33054
+ baker ga4 enhanced-measurement set --scrolls true --outbound-clicks true --file-downloads true
33055
+ baker ga4 enhanced-measurement set --site-search true --search-parameters "q,s,search,query,keyword"
33056
+ baker ga4 enhanced-measurement set --page-changes true # page views in a single-page app
33057
+ baker ga4 enhanced-measurement set --video false`
33058
+ },
33059
+ subCommands: {
33060
+ set: defineCommand120({
33061
+ meta: { name: "set", description: "Stage which automatic events the website stream collects" },
33062
+ args: {
33063
+ scrolls: { type: "string", description: "true|false", required: false },
33064
+ "outbound-clicks": { type: "string", description: "true|false", required: false },
33065
+ "site-search": { type: "string", description: "true|false", required: false },
33066
+ "search-parameters": { type: "string", description: "e.g. q,s,search,query,keyword", required: false },
33067
+ video: { type: "string", description: "true|false", required: false },
33068
+ "file-downloads": { type: "string", description: "true|false", required: false },
33069
+ "page-changes": { type: "string", description: "true|false", required: false },
33070
+ "form-interactions": { type: "string", description: "true|false", required: false },
33071
+ enabled: { type: "string", description: "true|false \u2014 master switch", required: false },
33072
+ "data-stream": { type: "string", description: "Stream id, when there are several", required: false },
33073
+ "property-id": propertyArg
33074
+ },
33075
+ run: async ({ args }) => {
33076
+ const payload = {};
33077
+ for (const [flag, field] of Object.entries(MEASUREMENT_FLAGS)) {
33078
+ const value = args[flag];
33079
+ if (typeof value === "string") {
33080
+ payload[field] = booleanArg(value, flag);
33081
+ }
33082
+ }
33083
+ if (typeof args["search-parameters"] === "string" && args["search-parameters"].length > 0) {
33084
+ payload.searchQueryParameter = args["search-parameters"];
33085
+ }
33086
+ if (Object.keys(payload).length === 0) {
33087
+ failValidation3(
33088
+ "pass at least one setting, e.g. --scrolls true --outbound-clicks true. Run `baker ga4 config --full` to see what is on today."
33089
+ );
33090
+ }
33091
+ await stageOp3({
33092
+ kind: "ga4.enhancedMeasurement.update",
33093
+ payload,
33094
+ ...typeof args["data-stream"] === "string" ? { dataStream: args["data-stream"] } : {},
33095
+ ...withProperty(args)
33096
+ });
33097
+ }
33098
+ })
33099
+ }
33100
+ });
32772
33101
 
32773
33102
  // src/commands/ga4/index.ts
32774
33103
  var ga4Command = defineCommand121({
@@ -32790,6 +33119,10 @@ Then stage changes (nothing is sent to Google Analytics until the chat completes
32790
33119
  baker ga4 key-event create --json '{"eventName":"generate_lead","countingMethod":"ONCE_PER_EVENT"}'
32791
33120
  baker ga4 custom-event create --json '{"destinationEvent":"quote_request","eventConditions":[...]}'
32792
33121
  baker ga4 custom-dimension create --json '{"parameterName":"plan_tier","displayName":"Plan tier","scope":"EVENT"}'
33122
+ baker ga4 audience create --json '{"displayName":"Visited without demo", \u2026}' \u2014 a group to retarget
33123
+ baker ga4 property set --industry TECHNOLOGY \u2014 benchmarking cohort
33124
+ baker ga4 ads-link create --customer-id 123-456-7890 \u2014 what makes audiences targetable
33125
+ baker ga4 enhanced-measurement set --scrolls true --outbound-clicks true \u2014 automatic events, no tag needed
32793
33126
  baker ga4 draft list \u2014 review everything staged
32794
33127
 
32795
33128
  An applied change takes effect on the property immediately \u2014 there is no version to publish afterwards
@@ -32807,6 +33140,10 @@ Full guide: __tooling__/docs/tools/baker/ga4.md`
32807
33140
  "custom-metric": customMetricCommand,
32808
33141
  "custom-event": customEventCommand,
32809
33142
  "data-retention": dataRetentionCommand,
33143
+ audience: audienceCommand,
33144
+ property: propertyCommand,
33145
+ "ads-link": adsLinkCommand,
33146
+ "enhanced-measurement": enhancedMeasurementCommand,
32810
33147
  draft: draftCommand3
32811
33148
  }
32812
33149
  });