@koda-sl/baker-cli 0.242.0 → 0.244.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
@@ -1186,6 +1186,8 @@ baker analytics geo --grain postal_code --country ES
1186
1186
  baker analytics bots # which crawlers fetch the site
1187
1187
  baker analytics presets # what each report answers
1188
1188
  baker analytics ads --platform google # what Google's traffic carries, in Google's vocabulary
1189
+ baker analytics tracking # do the ad URLs carry the campaign, ad group and ad at all
1190
+ baker analytics tracking --platform meta # the same for one platform, read from their own ad account
1189
1191
  baker analytics map --platform google --set kw=keyword # name a parameter, seen or not yet
1190
1192
  baker analytics map --platform google --remove kw # take that answer back
1191
1193
  baker analytics delivery --page 2 # the next page of a long list
@@ -1208,6 +1210,10 @@ Shared flags: `--days <n>` (default 30) or `--start-date` / `--end-date` (`YYYY-
1208
1210
 
1209
1211
  **And each `--set` is checked against the client's own ad account.** Baker reads their Google Ads tracking templates and final URL suffixes (or their Meta ads' URL parameters) and returns `evidence` per parameter: `confirmed` (their template really does fill that name with what you claimed), `contradicted` (it fills it with something else — `suggestedRole` says what), `unseen` (no template Baker can read mentions it, which is normal), `not_read` (no connection, or a platform with no template to read, with the reason). **`contradicted` is the one to stop for**: mapping a name your way when the account fills it another way produces a report that is wrong and looks entirely plausible.
1210
1212
 
1213
+ **`tracking` answers "can this account be reported on at all", and it reads the ad account to do it.** `analytics ads` can only describe parameters that have already arrived, which leaves two blind spots that mislead in the expensive direction: a platform with no traffic in the window looks exactly like a platform whose ads carry nothing, and a dimension arriving under a name nobody has mapped looks exactly like one nothing carries. `baker analytics tracking` reads the mapping, the traffic **and** the client's own Google tracking templates / Meta URL parameters, so it can answer for a platform that has not had a click yet. Every dimension comes back in one of four states, and they are four different jobs: `covered` is done; `unmapped` means the URLs already carry it under a name Baker does not read, so run the `mapCommand` in the response — free, and it repairs the traffic already collected; `missing` means nothing carries it; `unknown` means no visit arrived *and* the account could not be read, which is not a finding and must never be reported as untagged.
1214
+
1215
+ **`fix` says which level to put the missing pairs at, and who can write it.** Not a field name — the levels that platform accepts tagging at, widest first (the widest is the one to prefer: it covers campaigns nobody has created yet), which of them Baker can stage and which the user has to do by hand, and how each field treats a value already in it. That last part is the one people skip and it differs per platform: a Google **campaign** final URL suffix *replaces* the account one rather than merging, LinkedIn's `url-params` call *sets the ad set's whole parameter set* and Baker cannot read back what is in it, and a Meta creative is immutable so a live ad cannot be retagged through Baker at all. `suffix` is only ever the **missing** pairs, so the rule everywhere is the same: read what is there, work out what is missing, write the union. Where `fix` says Baker has no write surface — Microsoft, TikTok, Reddit, Pinterest, Snapchat — that is a request to hand the user with the exact string and the exact screen, never a reason to stop on the platforms it can write.
1216
+
1211
1217
  **The one recommendation about the ad account itself.** `baker analytics ads --platform <p>` warns when that platform's traffic carries no campaign id, ad group id or ad id — the three tiers every "which ad worked" question is grouped by — and returns the exact URL suffix, in the platform's own macro language, that would start sending the missing ones. It is the only mapping gap that cannot be fixed afterwards: naming a parameter Baker already captures corrects the history, because roles are resolved when a report is read, but a parameter the ads never sent has no stored values for a meaning to apply to. The hint stays quiet for a platform with little traffic, and defers to the untagged-account finding rather than repeating it.
1212
1218
 
1213
1219
  An unrecognised `--timezone` is refused with a `VALIDATION_ERROR` rather than defaulting to UTC, and `--output` accepts only `json` or `md`. Pass `--timezone` whenever the client is not in UTC. Day boundaries are drawn in UTC by default, which moves two hours of every Spanish evening onto the following day — not a rounding error on a day-of-week comparison, but the wrong day.
package/dist/cli.js CHANGED
@@ -2122,6 +2122,18 @@ var BIDDING_STRATEGY_TYPES = [
2122
2122
  "MANUAL_CPV",
2123
2123
  "PERCENT_CPC"
2124
2124
  ];
2125
+ var MERGED_TARGET_STRATEGY_CHANNELS = /* @__PURE__ */ new Set(["SEARCH"]);
2126
+ var MERGED_TARGET_CPA_MESSAGE = "Google doesn't accept Target CPA as a bidding strategy of its own on a Search campaign \u2014 the target sits inside Maximize conversions. Choose Maximize conversions and give it the same target CPA; it bids exactly the same.";
2127
+ var MERGED_TARGET_ROAS_MESSAGE = "Google doesn't accept Target ROAS as a bidding strategy of its own on a Search campaign \u2014 the target sits inside Maximize conversion value. Choose Maximize conversion value and give it the same target ROAS; it bids exactly the same.";
2128
+ function mergedTargetStrategyRefusal(channelType, biddingType) {
2129
+ if (channelType === void 0 || !MERGED_TARGET_STRATEGY_CHANNELS.has(channelType)) {
2130
+ return void 0;
2131
+ }
2132
+ if (biddingType === "TARGET_CPA") {
2133
+ return MERGED_TARGET_CPA_MESSAGE;
2134
+ }
2135
+ return biddingType === "TARGET_ROAS" ? MERGED_TARGET_ROAS_MESSAGE : void 0;
2136
+ }
2125
2137
  var POSITIVE_GEO_TARGET_TYPES = ["PRESENCE_OR_INTEREST", "PRESENCE"];
2126
2138
  var NEGATIVE_GEO_TARGET_TYPES = ["PRESENCE_OR_INTEREST", "PRESENCE"];
2127
2139
  var BUDGET_DELIVERY_METHODS = ["STANDARD", "ACCELERATED"];
@@ -2846,6 +2858,10 @@ var campaignCreateSchema2 = z4.strictObject({
2846
2858
  if (p.channelType === "PERFORMANCE_MAX" && p.bidding && p.bidding.type === "MANUAL_CPC") {
2847
2859
  ctx.addIssue({ code: "custom", path: ["bidding"], message: "Performance Max does not support Manual CPC" });
2848
2860
  }
2861
+ const mergedTarget = mergedTargetStrategyRefusal(p.channelType, p.bidding?.type);
2862
+ if (mergedTarget) {
2863
+ ctx.addIssue({ code: "custom", path: ["bidding"], message: mergedTarget });
2864
+ }
2849
2865
  if (p.startDate && p.endDate && p.endDate <= p.startDate) {
2850
2866
  ctx.addIssue({ code: "custom", path: ["endDate"], message: "endDate must be after startDate" });
2851
2867
  }
@@ -13628,10 +13644,16 @@ var campaignsCommand = defineCommand30({
13628
13644
  "budget-ref": { type: "string", description: "Budget ref (g_temp_*, id, or resource name)" },
13629
13645
  "bidding-strategy": {
13630
13646
  type: "string",
13631
- description: "MANUAL_CPC | MAXIMIZE_CONVERSIONS | TARGET_CPA | TARGET_ROAS | \u2026"
13647
+ description: "MANUAL_CPC | MAXIMIZE_CONVERSIONS | MAXIMIZE_CONVERSION_VALUE | TARGET_SPEND | \u2026"
13648
+ },
13649
+ "target-cpa": {
13650
+ type: "string",
13651
+ description: "Target CPA (major units). On a Search campaign pair it with --bidding-strategy MAXIMIZE_CONVERSIONS \u2014 Google keeps the target inside that strategy and rejects a standalone TARGET_CPA."
13652
+ },
13653
+ "target-roas": {
13654
+ type: "string",
13655
+ description: "Target ROAS (e.g. 4.0). On a Search campaign pair it with --bidding-strategy MAXIMIZE_CONVERSION_VALUE \u2014 Google keeps the target inside that strategy and rejects a standalone TARGET_ROAS."
13632
13656
  },
13633
- "target-cpa": { type: "string", description: "Target CPA (major units) for TARGET_CPA" },
13634
- "target-roas": { type: "string", description: "Target ROAS (e.g. 4.0) for TARGET_ROAS" },
13635
13657
  ...maxCpcArg,
13636
13658
  ...finalUrlSuffixArg,
13637
13659
  ...urlOptionsArgs,
@@ -13678,9 +13700,18 @@ var campaignsCommand = defineCommand30({
13678
13700
  ...fileArg,
13679
13701
  name: { type: "string" },
13680
13702
  "budget-ref": { type: "string" },
13681
- "bidding-strategy": { type: "string" },
13682
- "target-cpa": { type: "string" },
13683
- "target-roas": { type: "string" },
13703
+ "bidding-strategy": {
13704
+ type: "string",
13705
+ description: "MANUAL_CPC | MAXIMIZE_CONVERSIONS | MAXIMIZE_CONVERSION_VALUE | TARGET_SPEND | \u2026"
13706
+ },
13707
+ "target-cpa": {
13708
+ type: "string",
13709
+ description: "Target CPA (major units). On a Search campaign pair it with --bidding-strategy MAXIMIZE_CONVERSIONS \u2014 Google keeps the target inside that strategy and rejects a standalone TARGET_CPA."
13710
+ },
13711
+ "target-roas": {
13712
+ type: "string",
13713
+ description: "Target ROAS (e.g. 4.0). On a Search campaign pair it with --bidding-strategy MAXIMIZE_CONVERSION_VALUE \u2014 Google keeps the target inside that strategy and rejects a standalone TARGET_ROAS."
13714
+ },
13684
13715
  ...maxCpcArg,
13685
13716
  ...finalUrlSuffixArg,
13686
13717
  ...urlOptionsArgs,
@@ -14425,7 +14456,8 @@ function requireAudienceLevel(value) {
14425
14456
  return level;
14426
14457
  }
14427
14458
  var AUDIENCE_ATTACH_HINTS = [
14428
- 'Read back what the parent ended up with before you report: `baker ads google query "SELECT campaign.id, campaign.targeting_setting.target_restrictions FROM campaign"` (or the ad_group equivalent). A missing AUDIENCE row means TARGETING \u2014 Google does not serialize an empty setting, so absence is a value, not a gap.'
14459
+ 'Read back what the parent ended up with before you report: `baker ads google query "SELECT campaign.id, campaign.targeting_setting.target_restrictions FROM campaign"` (or the ad_group equivalent). A missing AUDIENCE row means TARGETING \u2014 Google does not serialize an empty setting, so absence is a value, not a gap.',
14460
+ "Staging checks the list is big enough to serve on this campaign's network (1,000 users on search, 100 on display) and refuses a targeting attach that is not. It does NOT check delivery afterwards, and Google reports no error when an audience stops a campaign serving \u2014 after publishing, read the campaign's impressions for the hour that follows and say what you saw."
14429
14461
  ];
14430
14462
  var audiencesAttachCommand = defineCommand30({
14431
14463
  meta: {