@koda-sl/baker-cli 0.434.0 → 0.435.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
@@ -621,6 +621,18 @@ baker ads google campaigns create --customer-id 1234567890 --name "Brand — Sea
621
621
  # New campaigns are declared as NOT containing EU political advertising (Google requires the
622
622
  # declaration on every create); pass --eu-political-ads only when the campaign does contain it.
623
623
  # On update, --eu-political-ads / --eu-political-ads=false corrects an existing declaration.
624
+ # Text guidelines (AI Max Search and Performance Max) stage on update only:
625
+ baker ads google campaigns update <campaign-id> --customer-id 1234567890 \
626
+ --term-exclusion "cheap" --messaging-restriction "Never promise a delivery date"
627
+ # --term-exclusion (repeatable, up to 25 × 30 chars) and --messaging-restriction (repeatable,
628
+ # up to 40 × 300 chars) each REPLACE the campaign's list; --clear-term-exclusions /
629
+ # --clear-messaging-restrictions empty one. A Search campaign needs AI Max with text
630
+ # customization on (in the same update or before), or staging is refused with the reason.
631
+ # AI Max for Search stages on create and update: --ai-max / --ai-max=false (off turns both
632
+ # features off too), --ai-max-text (text customization, needs AI Max) and
633
+ # --ai-max-url-expansion (final URL expansion, needs text customization). An omitted switch
634
+ # keeps the campaign's value; a combination Google refuses is refused at staging.
635
+ baker ads google campaigns update <campaign-id> --customer-id 1234567890 --ai-max --ai-max-text
624
636
  baker ads google ad-groups create --customer-id 1234567890 --name "Brand terms" --campaign-ref g_temp_<campaign>
625
637
  baker ads google keywords add --customer-id 1234567890 --ad-group-ref g_temp_<adgroup> --text "brand name" --match-type EXACT
626
638
  # Shared negative keyword list → attach to the campaign
package/dist/cli.js CHANGED
@@ -24891,7 +24891,13 @@ var GOOGLE_ADS_LIMITS = {
24891
24891
  /** `campaign.url_custom_parameters` — Google caps a single entity at 8 `{_name}` parameters. */
24892
24892
  urlCustomParametersMax: 8,
24893
24893
  urlCustomParameterKeyMax: 16,
24894
- urlCustomParameterValueMax: 250
24894
+ urlCustomParameterValueMax: 250,
24895
+ /** `campaign.text_guidelines.term_exclusions` — words AI Max and Performance Max must never write. */
24896
+ termExclusionsMax: 25,
24897
+ termExclusionMax: 30,
24898
+ /** `campaign.text_guidelines.messaging_restrictions` — freeform rules for generated text. */
24899
+ messagingRestrictionsMax: 40,
24900
+ messagingRestrictionMax: 300
24895
24901
  },
24896
24902
  adGroup: {
24897
24903
  nameMax: 255
@@ -25873,6 +25879,49 @@ var geoTargetTypeSettingSchema = z9.strictObject({
25873
25879
  (p) => p.positiveGeoTargetType !== void 0 || p.negativeGeoTargetType !== void 0,
25874
25880
  "geoTargetTypeSetting needs positiveGeoTargetType and/or negativeGeoTargetType"
25875
25881
  );
25882
+ var textGuidelinesSchema = z9.strictObject({
25883
+ termExclusions: z9.array(z9.string().trim().min(1).max(GOOGLE_ADS_LIMITS.campaign.termExclusionMax)).max(GOOGLE_ADS_LIMITS.campaign.termExclusionsMax).optional(),
25884
+ messagingRestrictions: z9.array(z9.string().trim().min(1).max(GOOGLE_ADS_LIMITS.campaign.messagingRestrictionMax)).max(GOOGLE_ADS_LIMITS.campaign.messagingRestrictionsMax).refine(
25885
+ (rules) => new Set(rules.map((rule) => rule.toLowerCase())).size === rules.length,
25886
+ "each messaging restriction must be unique"
25887
+ ).optional()
25888
+ }).refine(
25889
+ (p) => p.termExclusions !== void 0 || p.messagingRestrictions !== void 0,
25890
+ "textGuidelines needs termExclusions and/or messagingRestrictions"
25891
+ );
25892
+ var AI_MAX_TEXT_NEEDS_AI_MAX_MESSAGE = "AI Max text customization only works with AI Max on: also turn AI Max on, or leave text customization off.";
25893
+ var AI_MAX_URL_NEEDS_TEXT_MESSAGE = "AI Max final URL expansion only works with text customization on: also turn text customization on, or leave URL expansion off.";
25894
+ var aiMaxSchema = z9.strictObject({
25895
+ enabled: z9.boolean().optional(),
25896
+ textCustomization: z9.boolean().optional(),
25897
+ finalUrlExpansion: z9.boolean().optional()
25898
+ }).superRefine((p, ctx) => {
25899
+ if (p.enabled === void 0 && p.textCustomization === void 0 && p.finalUrlExpansion === void 0) {
25900
+ ctx.addIssue({ code: "custom", message: "aiMax needs enabled, textCustomization and/or finalUrlExpansion" });
25901
+ }
25902
+ if (p.enabled === false && p.textCustomization === true) {
25903
+ ctx.addIssue({ code: "custom", path: ["textCustomization"], message: AI_MAX_TEXT_NEEDS_AI_MAX_MESSAGE });
25904
+ }
25905
+ if (p.textCustomization === false && p.finalUrlExpansion === true) {
25906
+ ctx.addIssue({ code: "custom", path: ["finalUrlExpansion"], message: AI_MAX_URL_NEEDS_TEXT_MESSAGE });
25907
+ }
25908
+ });
25909
+ var AI_MAX_SEARCH_ONLY_MESSAGE = "AI Max exists only on Search campaigns.";
25910
+ function aiMaxCreateRefusal(channelType, aiMax) {
25911
+ if (!aiMax) {
25912
+ return void 0;
25913
+ }
25914
+ if (channelType !== "SEARCH") {
25915
+ return AI_MAX_SEARCH_ONLY_MESSAGE;
25916
+ }
25917
+ if (aiMax.enabled !== true && aiMax.textCustomization === true) {
25918
+ return AI_MAX_TEXT_NEEDS_AI_MAX_MESSAGE;
25919
+ }
25920
+ if (aiMax.textCustomization !== true && aiMax.finalUrlExpansion === true) {
25921
+ return AI_MAX_URL_NEEDS_TEXT_MESSAGE;
25922
+ }
25923
+ return void 0;
25924
+ }
25876
25925
  var campaignCreateSchema2 = z9.strictObject({
25877
25926
  name: z9.string().min(1).max(GOOGLE_ADS_LIMITS.campaign.nameMax),
25878
25927
  channelType: z9.enum(ADVERTISING_CHANNEL_TYPES),
@@ -25902,6 +25951,8 @@ var campaignCreateSchema2 = z9.strictObject({
25902
25951
  * every campaign create (FieldError.REQUIRED without it); omitted means it does not.
25903
25952
  */
25904
25953
  euPoliticalAds: z9.boolean().optional(),
25954
+ /** AI Max for Search. A new campaign has it off unless this turns it on. */
25955
+ aiMax: aiMaxSchema.optional(),
25905
25956
  status: stageableStatusSchema2.default("PAUSED")
25906
25957
  }).superRefine((p, ctx) => {
25907
25958
  if (!p.bidding && !p.biddingStrategy) {
@@ -25928,6 +25979,10 @@ var campaignCreateSchema2 = z9.strictObject({
25928
25979
  if (p.startDate && p.endDate && p.endDate <= p.startDate) {
25929
25980
  ctx.addIssue({ code: "custom", path: ["endDate"], message: "endDate must be after startDate" });
25930
25981
  }
25982
+ const aiMaxRefusal = aiMaxCreateRefusal(p.channelType, p.aiMax);
25983
+ if (aiMaxRefusal) {
25984
+ ctx.addIssue({ code: "custom", path: ["aiMax"], message: aiMaxRefusal });
25985
+ }
25931
25986
  });
25932
25987
  var campaignUpdateSchema2 = z9.strictObject({
25933
25988
  name: z9.string().min(1).max(GOOGLE_ADS_LIMITS.campaign.nameMax).optional(),
@@ -25946,6 +26001,10 @@ var campaignUpdateSchema2 = z9.strictObject({
25946
26001
  urlCustomParameters: urlCustomParametersSchema.optional(),
25947
26002
  /** Corrects the campaign's EU political advertising declaration (true = contains, false = does not). */
25948
26003
  euPoliticalAds: z9.boolean().optional(),
26004
+ /** AI Max / Performance Max text guidelines. Google refuses them on a Search campaign without AI Max. */
26005
+ textGuidelines: textGuidelinesSchema.optional(),
26006
+ /** Turns AI Max or its features on or off. Search campaigns only (checked against the live channel). */
26007
+ aiMax: aiMaxSchema.optional(),
25949
26008
  status: z9.enum(["ENABLED", "PAUSED", "REMOVED"]).optional()
25950
26009
  }).refine((p) => Object.values(p).some((v) => v !== void 0), "update needs at least one field");
25951
26010
  var adGroupCreateSchema = z9.strictObject({
@@ -49037,6 +49096,74 @@ function urlCustomParametersFromFlags(args8, rawArgs) {
49037
49096
  return { key: entry.slice(0, split), value: entry.slice(split + 1) };
49038
49097
  });
49039
49098
  }
49099
+ var aiMaxArgs = {
49100
+ "ai-max": {
49101
+ type: "boolean",
49102
+ description: "AI Max for Search: --ai-max turns it on (Google then matches searches beyond your keywords), --ai-max=false turns it off along with its text customization and URL expansion. Search campaigns only."
49103
+ },
49104
+ "ai-max-text": {
49105
+ type: "boolean",
49106
+ description: "AI Max text customization: --ai-max-text lets Google write ad text from your landing page and ads (needs AI Max on); --ai-max-text=false stops it. Required before text guidelines."
49107
+ },
49108
+ "ai-max-url-expansion": {
49109
+ type: "boolean",
49110
+ description: "AI Max final URL expansion: --ai-max-url-expansion lets Google send clicks to the most relevant page of your site (needs text customization on); --ai-max-url-expansion=false keeps every click on your final URLs."
49111
+ }
49112
+ };
49113
+ function aiMaxFromFlags(args8) {
49114
+ const flag2 = (name) => typeof args8[name] === "boolean" ? args8[name] : void 0;
49115
+ const aiMax = {
49116
+ enabled: flag2("ai-max"),
49117
+ textCustomization: flag2("ai-max-text"),
49118
+ finalUrlExpansion: flag2("ai-max-url-expansion")
49119
+ };
49120
+ const stated = Object.entries(aiMax).filter(([, v]) => v !== void 0);
49121
+ return stated.length > 0 ? Object.fromEntries(stated) : void 0;
49122
+ }
49123
+ var textGuidelinesArgs = {
49124
+ "term-exclusion": {
49125
+ type: "string",
49126
+ description: `Text guidelines (AI Max Search and Performance Max): a word or phrase Google's generated ad text must never use, up to 30 characters (e.g. --term-exclusion "cheap"). Repeat for several; Google allows 25. REPLACES the campaign's whole list.`
49127
+ },
49128
+ "messaging-restriction": {
49129
+ type: "string",
49130
+ description: `Text guidelines (AI Max Search and Performance Max): a plain-language rule for Google's generated ad text, up to 300 characters (e.g. --messaging-restriction "Never promise a delivery date"). Repeat for several; Google allows 40. REPLACES the campaign's whole list.`
49131
+ },
49132
+ "clear-term-exclusions": {
49133
+ type: "boolean",
49134
+ description: "Remove every term exclusion from this campaign's text guidelines."
49135
+ },
49136
+ "clear-messaging-restrictions": {
49137
+ type: "boolean",
49138
+ description: "Remove every messaging restriction from this campaign's text guidelines."
49139
+ }
49140
+ };
49141
+ function guidelineListFromFlags(args8, rawArgs, flag2, clearFlag) {
49142
+ const values = repeatedValues(rawArgs, flag2, args8[flag2]).filter((v) => v.length > 0);
49143
+ if (values.length === 0) {
49144
+ return args8[clearFlag] === true ? [] : void 0;
49145
+ }
49146
+ if (args8[clearFlag] === true) {
49147
+ failWriteValidation2(`--${clearFlag} removes the whole list \u2014 pass it OR --${flag2}, not both`);
49148
+ }
49149
+ return values;
49150
+ }
49151
+ function textGuidelinesFromFlags(args8, rawArgs) {
49152
+ const termExclusions = guidelineListFromFlags(args8, rawArgs, "term-exclusion", "clear-term-exclusions");
49153
+ const messagingRestrictions = guidelineListFromFlags(
49154
+ args8,
49155
+ rawArgs,
49156
+ "messaging-restriction",
49157
+ "clear-messaging-restrictions"
49158
+ );
49159
+ if (termExclusions === void 0 && messagingRestrictions === void 0) {
49160
+ return void 0;
49161
+ }
49162
+ return {
49163
+ ...termExclusions !== void 0 ? { termExclusions } : {},
49164
+ ...messagingRestrictions !== void 0 ? { messagingRestrictions } : {}
49165
+ };
49166
+ }
49040
49167
  var geoTargetTypeArgs = {
49041
49168
  "geo-target-type": {
49042
49169
  type: "string",
@@ -49188,6 +49315,7 @@ var campaignsCommand3 = defineCommand57({
49188
49315
  ...urlOptionsArgs,
49189
49316
  ...geoTargetTypeArgs,
49190
49317
  ...networksArg,
49318
+ ...aiMaxArgs,
49191
49319
  objective: { type: "string", description: "Advisory UI objective (SALES, LEADS, \u2026)" },
49192
49320
  "start-date": { type: "string", description: "YYYY-MM-DD" },
49193
49321
  "end-date": { type: "string", description: "YYYY-MM-DD" },
@@ -49214,6 +49342,7 @@ var campaignsCommand3 = defineCommand57({
49214
49342
  startDate: args8["start-date"],
49215
49343
  endDate: args8["end-date"],
49216
49344
  euPoliticalAds: args8["eu-political-ads"],
49345
+ aiMax: aiMaxFromFlags(args8),
49217
49346
  status: args8.status
49218
49347
  });
49219
49348
  await stageCreate("google.campaign.create", customerId, payload, [
@@ -49225,7 +49354,7 @@ var campaignsCommand3 = defineCommand57({
49225
49354
  update: defineCommand57({
49226
49355
  meta: {
49227
49356
  name: "update",
49228
- description: `Stage a change to an existing campaign's name, budget, bidding or settings. ${STAGED_WRITE}`
49357
+ description: `Stage a change to an existing campaign's name, budget, bidding, settings, AI Max switches or text guidelines (what AI Max and Performance Max ad text must avoid). ${STAGED_WRITE}`
49229
49358
  },
49230
49359
  args: {
49231
49360
  ...customerIdArg,
@@ -49253,6 +49382,8 @@ var campaignsCommand3 = defineCommand57({
49253
49382
  ...urlOptionsArgs,
49254
49383
  ...geoTargetTypeArgs,
49255
49384
  ...networksArg,
49385
+ ...textGuidelinesArgs,
49386
+ ...aiMaxArgs,
49256
49387
  "eu-political-ads": {
49257
49388
  type: "boolean",
49258
49389
  description: "Corrects the campaign's EU political advertising declaration: --eu-political-ads if it contains EU political ads, --eu-political-ads=false if it does not. Existing campaigns without a declaration block location-targeting changes until one is set."
@@ -49283,6 +49414,8 @@ var campaignsCommand3 = defineCommand57({
49283
49414
  trackingUrlTemplate: args8["tracking-template"],
49284
49415
  urlCustomParameters: urlCustomParametersFromFlags(args8, rawArgs),
49285
49416
  euPoliticalAds: args8["eu-political-ads"],
49417
+ textGuidelines: textGuidelinesFromFlags(args8, rawArgs),
49418
+ aiMax: aiMaxFromFlags(args8),
49286
49419
  status: args8.status
49287
49420
  });
49288
49421
  await stageUpdate("google.campaign.update", customerId, target2, payload);