@koda-sl/baker-cli 0.173.0-dev.6ecd65d29 → 0.173.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/dist/cli.js CHANGED
@@ -36,7 +36,7 @@ import {
36
36
  toModelSafeImage,
37
37
  ulid,
38
38
  validateCanvasDeep
39
- } from "./chunk-K424D3CY.js";
39
+ } from "./chunk-CG6OTNUU.js";
40
40
  import {
41
41
  csvOrJson,
42
42
  daysAgoIso,
@@ -3063,6 +3063,13 @@ var hubspotContactLookupResponseSchema = z10.object({
3063
3063
  createdAt: z10.string().nullable()
3064
3064
  }).nullable(),
3065
3065
  deals: z10.array(z10.object({ id: z10.string(), stage: z10.string().nullable(), pipeline: z10.string().nullable() })),
3066
+ /**
3067
+ * False when the connection never granted deal access, which makes an empty
3068
+ * `deals` unknown rather than absent. Deal access is granted separately from
3069
+ * contact access, so one can be present without the other — and "no deals"
3070
+ * is the headline finding of this command, so it must not be guessed.
3071
+ */
3072
+ dealsReadable: z10.boolean(),
3066
3073
  /**
3067
3074
  * The account the contact was filed under. In B2B a workflow usually
3068
3075
  * creates it on contact creation, so its absence is a finding — but only
@@ -4977,6 +4984,19 @@ var ASSET_FIELD_TYPES = [
4977
4984
  "MARKETING_IMAGE",
4978
4985
  "BUSINESS_NAME"
4979
4986
  ];
4987
+ var ASSET_GROUP_ASSET_FIELD_TYPES = [
4988
+ "MARKETING_IMAGE",
4989
+ "SQUARE_MARKETING_IMAGE",
4990
+ "PORTRAIT_MARKETING_IMAGE",
4991
+ "LOGO",
4992
+ "LANDSCAPE_LOGO",
4993
+ "YOUTUBE_VIDEO",
4994
+ "HEADLINE",
4995
+ "LONG_HEADLINE",
4996
+ "DESCRIPTION",
4997
+ "BUSINESS_NAME",
4998
+ "CALL_TO_ACTION_SELECTION"
4999
+ ];
4980
5000
  var DEVICE_TYPES = ["MOBILE", "TABLET", "DESKTOP", "CONNECTED_TV", "OTHER"];
4981
5001
  var DAYS_OF_WEEK = ["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY"];
4982
5002
  var CAMPAIGN_OBJECTIVES = [
@@ -5353,9 +5373,15 @@ var assetGroupCreateSchema = z15.object({
5353
5373
  businessName: z15.string().min(1).max(GOOGLE_ADS_LIMITS.assetGroup.businessNameMax),
5354
5374
  imageAssets: z15.array(refSchema).optional(),
5355
5375
  squareImageAssets: z15.array(refSchema).optional(),
5376
+ portraitImageAssets: z15.array(refSchema).optional(),
5356
5377
  logoAssets: z15.array(refSchema).optional(),
5357
5378
  status: z15.enum(["ENABLED", "PAUSED"]).default("PAUSED")
5358
5379
  });
5380
+ var assetGroupAssetAttachSchema = z15.object({
5381
+ assetGroup: refSchema,
5382
+ asset: refSchema,
5383
+ fieldType: z15.enum(ASSET_GROUP_ASSET_FIELD_TYPES)
5384
+ });
5359
5385
  var assetGroupUpdateSchema = z15.object({
5360
5386
  name: z15.string().min(1).max(GOOGLE_ADS_LIMITS.assetGroup.nameMax).optional(),
5361
5387
  finalUrls: z15.array(httpsUrlSchema2).min(1).optional(),
@@ -5526,6 +5552,8 @@ var GOOGLE_DRAFT_OP_KINDS = [
5526
5552
  "google.assetLink.detach",
5527
5553
  "google.assetGroup.create",
5528
5554
  "google.assetGroup.update",
5555
+ "google.assetGroupAsset.attach",
5556
+ "google.assetGroupAsset.detach",
5529
5557
  "google.audience.create",
5530
5558
  "google.audienceCriterion.attach",
5531
5559
  "google.audienceCriterion.detach",
@@ -5583,6 +5611,8 @@ var googleDraftOpInputSchema = z15.discriminatedUnion("kind", [
5583
5611
  targetOp("google.assetLink.detach"),
5584
5612
  createOp2("google.assetGroup.create", assetGroupCreateSchema),
5585
5613
  updateOp2("google.assetGroup.update", assetGroupUpdateSchema),
5614
+ createOp2("google.assetGroupAsset.attach", assetGroupAssetAttachSchema),
5615
+ targetOp("google.assetGroupAsset.detach"),
5586
5616
  createOp2("google.audience.create", audienceCreateSchema2),
5587
5617
  createOp2("google.audienceCriterion.attach", audienceCriterionAttachSchema),
5588
5618
  targetOp("google.audienceCriterion.detach"),
@@ -9553,8 +9583,15 @@ var assetGroupsCommand = defineCommand30({
9553
9583
  "long-headlines": { type: "string", description: "Comma-separated long headlines (1\u20135, \u226490 chars)" },
9554
9584
  descriptions: { type: "string", description: "Comma-separated descriptions (2\u20135, \u226490 chars)" },
9555
9585
  "business-name": { type: "string" },
9556
- "image-assets": { type: "string", description: "Comma-separated marketing image asset refs" },
9557
- "square-image-assets": { type: "string", description: "Comma-separated square marketing image asset refs" },
9586
+ "image-assets": { type: "string", description: "Comma-separated marketing image asset refs (1.91:1)" },
9587
+ "square-image-assets": {
9588
+ type: "string",
9589
+ description: "Comma-separated square marketing image asset refs (1:1)"
9590
+ },
9591
+ "portrait-image-assets": {
9592
+ type: "string",
9593
+ description: "Comma-separated portrait marketing image asset refs (4:5)"
9594
+ },
9558
9595
  "logo-assets": { type: "string", description: "Comma-separated logo image asset refs" },
9559
9596
  status: { type: "string", description: "ENABLED | PAUSED (default PAUSED)" }
9560
9597
  },
@@ -9573,6 +9610,7 @@ var assetGroupsCommand = defineCommand30({
9573
9610
  businessName: args["business-name"],
9574
9611
  imageAssets: listFlag(args["image-assets"]),
9575
9612
  squareImageAssets: listFlag(args["square-image-assets"]),
9613
+ portraitImageAssets: listFlag(args["portrait-image-assets"]),
9576
9614
  logoAssets: listFlag(args["logo-assets"]),
9577
9615
  status: args.status
9578
9616
  });
@@ -9598,7 +9636,40 @@ var assetGroupsCommand = defineCommand30({
9598
9636
  });
9599
9637
  await stageUpdate("google.assetGroup.update", customerId, target, payload);
9600
9638
  }
9601
- })
9639
+ }),
9640
+ attach: defineCommand30({
9641
+ meta: {
9642
+ name: "attach",
9643
+ description: "Add one existing asset to an existing asset group. Start here to refresh a live PMax group's creatives: attach the new image, then `detach` the old one \u2014 never detach first."
9644
+ },
9645
+ args: {
9646
+ ...customerIdArg,
9647
+ ...fileArg,
9648
+ "asset-group-ref": { type: "string", description: "Asset group ref (g_temp_*, id, or resource name)" },
9649
+ "asset-ref": { type: "string", description: "Asset ref (g_temp_*, id, or resource name)" },
9650
+ "field-type": {
9651
+ type: "string",
9652
+ description: `Slot the asset fills: ${ASSET_GROUP_ASSET_FIELD_TYPES.join(" | ")}`
9653
+ }
9654
+ },
9655
+ run: async ({ args }) => {
9656
+ const customerId = requireCustomerId(args);
9657
+ const payload = mergePayload(loadJsonFileArg(args.file), {
9658
+ assetGroup: args["asset-group-ref"],
9659
+ asset: args["asset-ref"],
9660
+ fieldType: args["field-type"]
9661
+ });
9662
+ await stageCreate("google.assetGroupAsset.attach", customerId, payload, [
9663
+ "Swapping a creative? Stage the matching `asset-groups detach` for the image this one replaces \u2014 an attach alone grows the group instead of refreshing it.",
9664
+ "The field type must match the image's real ratio (1.91:1 \u2192 MARKETING_IMAGE, 1:1 \u2192 SQUARE_MARKETING_IMAGE, 4:5 \u2192 PORTRAIT_MARKETING_IMAGE); Google rejects a mismatch at publish.",
9665
+ "Slots have ceilings as well as minimums: BUSINESS_NAME and CALL_TO_ACTION_SELECTION hold 1, LOGO 5, LONG_HEADLINE and DESCRIPTION 5, HEADLINE 15, YOUTUBE_VIDEO 15, images 20. Adding to a full slot is rejected exactly like removing below an empty one, so count what the group already holds (`SELECT asset_group_asset.field_type FROM asset_group_asset WHERE asset_group.id = \u2026`) and stage the detach alongside when it is full."
9666
+ ]);
9667
+ }
9668
+ }),
9669
+ detach: statusCommand2("google.assetGroupAsset.detach", "asset group asset", [
9670
+ "Stage order is execution order \u2014 if you are replacing this image, its `asset-groups attach` must already be staged BEFORE this detach, or Google sees the group below its per-ratio minimum and rejects the removal.",
9671
+ "Pass the full composite resource name (customers/{cid}/assetGroupAssets/{assetGroupId}~{assetId}~{FIELD_TYPE}) \u2014 read it from `asset_group_asset.resource_name` via `ads google query`."
9672
+ ])
9602
9673
  }
9603
9674
  });
9604
9675
  function fileCreateCommand(name, kind, description, hints) {
@@ -26708,7 +26779,7 @@ registerSchema({
26708
26779
  });
26709
26780
  registerSchema({
26710
26781
  command: "hubspot.contacts.lookup",
26711
- description: "One contact by email: its lifecycle stage, whether anyone is assigned, the account (company) it was filed under, and any deals it is attached to with their stage. Use it to settle whether a specific lead was picked up. `found: false` means the lead never reached the CRM at all \u2014 that is a finding, not an error. `companyReadable: false` means account details were not readable on this connection, so a null company is unknown rather than absent.",
26782
+ description: "One contact by email: its lifecycle stage, whether anyone is assigned, the account (company) it was filed under, and any deals it is attached to with their stage. Use it to settle whether a specific lead was picked up. `found: false` means the lead never reached the CRM at all \u2014 that is a finding, not an error. `dealsReadable: false` / `companyReadable: false` mean deals or account details were not readable on this connection, so an empty deals list or a null company is unknown rather than absent.",
26712
26783
  args: {
26713
26784
  email: { type: "positional", description: "The contact's email address", required: true }
26714
26785
  }
@@ -27078,6 +27149,35 @@ var contactsSummaryCommand = defineCommand114({
27078
27149
  }
27079
27150
  }
27080
27151
  });
27152
+ function contactLookupHints(data) {
27153
+ const { found, contact, deals, dealsReadable, company, companyReadable } = data;
27154
+ if (!found) {
27155
+ return [
27156
+ "No contact with that email. The lead never reached the CRM \u2014 check the form's submissions before assuming the CRM lost it."
27157
+ ];
27158
+ }
27159
+ const hints = [];
27160
+ if (contact?.ownerId === null) {
27161
+ hints.push("Nobody is assigned to this contact.");
27162
+ }
27163
+ if (!dealsReadable) {
27164
+ hints.push(
27165
+ "Deals were not read: this HubSpot connection does not cover them. Do not report the lead as having no deal \u2014 you cannot tell. The user can enable it by reconnecting HubSpot."
27166
+ );
27167
+ } else if (deals.length === 0) {
27168
+ hints.push("No deal is associated, so this contact was never taken into the pipeline.");
27169
+ }
27170
+ if (!companyReadable) {
27171
+ hints.push(
27172
+ "Account details were not read: this HubSpot connection does not cover them. Do not report the lead as having no account \u2014 you cannot tell. The user can enable it by reconnecting HubSpot."
27173
+ );
27174
+ } else if (company === null) {
27175
+ hints.push(
27176
+ "No account is associated either. Most B2B setups create one automatically when a contact arrives, so this usually means the lead was never processed at all \u2014 not that the account is missing."
27177
+ );
27178
+ }
27179
+ return hints;
27180
+ }
27081
27181
  var contactsLookupCommand = defineCommand114({
27082
27182
  meta: {
27083
27183
  name: "lookup",
@@ -27091,31 +27191,12 @@ var contactsLookupCommand = defineCommand114({
27091
27191
  const response = await apiPost("/api/hubspot/contacts/lookup", {
27092
27192
  email: args.email
27093
27193
  });
27094
- const { found, contact, deals, company, companyReadable } = response.data;
27095
- const hints = [];
27096
- if (!found) {
27097
- hints.push(
27098
- "No contact with that email. The lead never reached the CRM \u2014 check the form's submissions before assuming the CRM lost it."
27099
- );
27100
- } else {
27101
- if (contact?.ownerId === null) {
27102
- hints.push("Nobody is assigned to this contact.");
27103
- }
27104
- if (deals.length === 0) {
27105
- hints.push("No deal is associated, so this contact was never taken into the pipeline.");
27106
- }
27107
- if (companyReadable && company === null) {
27108
- hints.push(
27109
- "No account is associated either. Most B2B setups create one automatically when a contact arrives, so this usually means the lead was never processed at all \u2014 not that the account is missing."
27110
- );
27111
- }
27112
- if (!companyReadable) {
27113
- hints.push(
27114
- "Account details were not read: this HubSpot connection does not cover them. Do not report the lead as having no account \u2014 you cannot tell. The user can enable it by reconnecting HubSpot."
27115
- );
27116
- }
27117
- }
27118
- writeJson({ ...response, meta: { count: found ? 1 : 0 }, ...hints.length > 0 ? { hints } : {} });
27194
+ const hints = contactLookupHints(response.data);
27195
+ writeJson({
27196
+ ...response,
27197
+ meta: { count: response.data.found ? 1 : 0 },
27198
+ ...hints.length > 0 ? { hints } : {}
27199
+ });
27119
27200
  } catch (err) {
27120
27201
  failNotConnected(err);
27121
27202
  }