@koda-sl/baker-cli 0.139.0 → 0.140.1

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
@@ -672,6 +672,7 @@ baker ads meta campaigns create --account-id act_123 --name "Q3 Prospecting" -
672
672
  baker ads meta adsets create --account-id act_123 --campaign meta_temp_camp --name "US 25-54" \
673
673
  --optimization-goal OFFSITE_CONVERSIONS --billing-event IMPRESSIONS --pixel-id 111 --custom-event-type LEAD \
674
674
  --targeting-file targeting.json # → meta_temp_as
675
+ # lead-form goals (LEAD_GENERATION / QUALITY_LEAD) and PAGE_LIKES require --page-id (the Facebook Page), else Meta rejects the ad set
675
676
  baker ads meta creatives create --account-id act_123 --page 555 --instagram-user 777 \
676
677
  --message "Save money on pet insurance" --link https://example.com --headline "Best cover" \
677
678
  --image-ref meta_temp_img --cta SHOP_NOW --standard-enhancements on # → meta_temp_cr
package/dist/cli.js CHANGED
@@ -73,7 +73,7 @@ import {
73
73
  } from "./chunk-RK67WL4O.js";
74
74
 
75
75
  // src/cli.ts
76
- import { defineCommand as defineCommand174, runMain } from "citty";
76
+ import { defineCommand as defineCommand175, runMain } from "citty";
77
77
 
78
78
  // src/commands/actions/index.ts
79
79
  import { defineCommand as defineCommand18 } from "citty";
@@ -1008,6 +1008,33 @@ var followResponseSchema = z2.object({
1008
1008
  preview: resolvePreviewSchema.nullable(),
1009
1009
  candidates: z2.array(corpusAdvertiserSchema).nullable()
1010
1010
  });
1011
+ var FOLLOW_BATCH_MAX = 50;
1012
+ var followBatchRequestSchema = z2.object({
1013
+ // One entry per brand — a domain (best; resolves both Meta + LinkedIn), a
1014
+ // profile / ad-library URL, or a brand name.
1015
+ inputs: z2.array(z2.string().min(1)).min(1).max(FOLLOW_BATCH_MAX),
1016
+ // How to read every input (not a tracking limit — a domain still resolves
1017
+ // every platform we can). Defaults to reading inputs as Meta identities.
1018
+ platform: adLibraryPlatformSchema.default("meta")
1019
+ });
1020
+ var followBatchResultSchema = z2.object({
1021
+ // Echoes the input so the caller can line results up with what it sent.
1022
+ input: z2.string(),
1023
+ // "following"/"added" mirror the single-follow statuses; "ambiguous" needs a
1024
+ // manual pick; "failed" = we couldn't resolve or add it (see `error`).
1025
+ status: z2.enum(["following", "added", "ambiguous", "failed"]),
1026
+ advertiser: followedAdvertiserSchema.nullable(),
1027
+ // Present only when status is "failed" — a plain-language reason.
1028
+ error: z2.string().nullable()
1029
+ });
1030
+ var followBatchResponseSchema = z2.object({
1031
+ results: z2.array(followBatchResultSchema),
1032
+ // Rollup counts across the batch for a one-glance summary.
1033
+ followed_count: z2.number(),
1034
+ added_count: z2.number(),
1035
+ ambiguous_count: z2.number(),
1036
+ failed_count: z2.number()
1037
+ });
1011
1038
  var unfollowRequestSchema = z2.object({
1012
1039
  // Accept either the Baker subscription id or the corpus advertiser id.
1013
1040
  followed_id: z2.string().optional(),
@@ -11365,6 +11392,11 @@ var OPTIMIZATION_GOALS = [
11365
11392
  "CONVERSATIONS",
11366
11393
  "DERIVED_EVENTS"
11367
11394
  ];
11395
+ var OPTIMIZATION_GOALS_REQUIRING_PAGE = [
11396
+ "LEAD_GENERATION",
11397
+ "QUALITY_LEAD",
11398
+ "PAGE_LIKES"
11399
+ ];
11368
11400
  var DESTINATION_TYPES = [
11369
11401
  "WEBSITE",
11370
11402
  "APP",
@@ -11582,8 +11614,18 @@ function validateAdSetBudgetAndBid(p, ctx) {
11582
11614
  ctx.addIssue({ code: "custom", path: ["end_time"], message: "end_time must be after start_time" });
11583
11615
  }
11584
11616
  }
11617
+ function validateAdSetPromotedObject(p, ctx) {
11618
+ if (p.optimization_goal && OPTIMIZATION_GOALS_REQUIRING_PAGE.includes(p.optimization_goal) && !p.promoted_object?.page_id) {
11619
+ ctx.addIssue({
11620
+ code: "custom",
11621
+ path: ["promoted_object", "page_id"],
11622
+ message: `optimization_goal ${p.optimization_goal} needs promoted_object.page_id \u2014 pass --page-id with the Facebook Page the leads/likes belong to`
11623
+ });
11624
+ }
11625
+ }
11585
11626
  var adSetCreateSchema = z14.object(adSetFields).superRefine((p, ctx) => {
11586
11627
  validateAdSetBudgetAndBid(p, ctx);
11628
+ validateAdSetPromotedObject(p, ctx);
11587
11629
  });
11588
11630
  var adSetUpdateSchema = z14.object({
11589
11631
  name: adSetFields.name.optional(),
@@ -11604,6 +11646,9 @@ var adSetUpdateSchema = z14.object({
11604
11646
  ctx.addIssue({ code: "custom", message: "update needs at least one field" });
11605
11647
  }
11606
11648
  validateAdSetBudgetAndBid(p, ctx);
11649
+ if (p.optimization_goal) {
11650
+ validateAdSetPromotedObject(p, ctx);
11651
+ }
11607
11652
  });
11608
11653
  var messageSchema = z14.string().min(1).max(META_LIMITS.creative.messageHardMax);
11609
11654
  var headlineSchema2 = z14.string().min(1).max(META_LIMITS.creative.headlineMax);
@@ -12191,7 +12236,10 @@ var adSetWriteArgs = {
12191
12236
  end: { type: "string", description: "End time" },
12192
12237
  "targeting-file": { type: "string", description: "JSON file with the full Meta targeting spec" },
12193
12238
  "promoted-object-file": { type: "string", description: "JSON file with the promoted_object" },
12194
- "page-id": { type: "string", description: "promoted_object.page_id shortcut" },
12239
+ "page-id": {
12240
+ type: "string",
12241
+ description: "promoted_object.page_id shortcut (required for LEAD_GENERATION / QUALITY_LEAD / PAGE_LIKES goals)"
12242
+ },
12195
12243
  "pixel-id": { type: "string", description: "promoted_object.pixel_id shortcut" },
12196
12244
  "custom-event-type": { type: "string", description: "promoted_object.custom_event_type (PURCHASE|LEAD|\u2026)" },
12197
12245
  file: { type: "string", description: "JSON file with the full payload; flags override" }
@@ -12201,6 +12249,7 @@ var adSetsCreateCommand = defineCommand54({
12201
12249
  name: "create",
12202
12250
  description: `Stage a new ad set. ${STAGED_NOTE2}
12203
12251
  Chain to a campaign with --campaign (real id or meta_temp_* ref).
12252
+ Lead-form goals (LEAD_GENERATION, QUALITY_LEAD) and PAGE_LIKES need --page-id (the Facebook Page the leads/likes belong to).
12204
12253
  Example: baker ads meta adsets create --campaign meta_temp_ab12 --name "US 25-54" --optimization-goal LINK_CLICKS --billing-event IMPRESSIONS --daily-budget 50 --targeting-file t.json`
12205
12254
  },
12206
12255
  args: { campaign: { type: "string", description: "Parent campaign id or meta_temp_* ref" }, ...adSetWriteArgs },
@@ -12214,6 +12263,7 @@ var adSetsUpdateCommand = defineCommand54({
12214
12263
  meta: {
12215
12264
  name: "update",
12216
12265
  description: `Stage changes to an ad set. ${STAGED_NOTE2}
12266
+ Switching --optimization-goal to a lead-form goal (LEAD_GENERATION, QUALITY_LEAD) or PAGE_LIKES needs --page-id in the same change.
12217
12267
  Example: baker ads meta adsets update 6123 --daily-budget 80`
12218
12268
  },
12219
12269
  args: { id: { type: "positional", description: "Ad set id or meta_temp_* ref", required: true }, ...adSetWriteArgs },
@@ -27994,7 +28044,7 @@ Examples:
27994
28044
  });
27995
28045
 
27996
28046
  // src/commands/winning-ads/index.ts
27997
- import { defineCommand as defineCommand173 } from "citty";
28047
+ import { defineCommand as defineCommand174 } from "citty";
27998
28048
 
27999
28049
  // src/commands/winning-ads/advertisers.ts
28000
28050
  import { defineCommand as defineCommand163 } from "citty";
@@ -28388,8 +28438,116 @@ var followCommand = defineCommand166({
28388
28438
  }
28389
28439
  });
28390
28440
 
28391
- // src/commands/winning-ads/following.ts
28441
+ // src/commands/winning-ads/follow-competitors.ts
28392
28442
  import { defineCommand as defineCommand167 } from "citty";
28443
+ var PLATFORMS2 = ["meta", "linkedin"];
28444
+ var BATCH_TIMEOUT_MS = 3e5;
28445
+ function buildFollowBatchBody(input) {
28446
+ const seen = /* @__PURE__ */ new Set();
28447
+ const inputs = [];
28448
+ for (const domain of splitList(input.domains)) {
28449
+ const key = domain.toLowerCase();
28450
+ if (seen.has(key)) {
28451
+ continue;
28452
+ }
28453
+ seen.add(key);
28454
+ inputs.push(domain);
28455
+ }
28456
+ return { inputs, platform: input.platform ?? "meta" };
28457
+ }
28458
+ registerSchema({
28459
+ command: "winning-ads.follow-competitors",
28460
+ description: "Follow MANY brands at once \u2014 the fast way to add every competitor to your library. Pass a comma-separated list of domains (best \u2014 each resolves both Meta + LinkedIn, every country). One brand failing never blocks the others: the response reports per-brand status (following/added/ambiguous/failed) plus rollup counts. Ideal right after a competitor scan.",
28461
+ args: {
28462
+ domains: {
28463
+ type: "string",
28464
+ description: `Comma-separated domains (best), profile/ad-library URLs, or brand names \u2014 up to ${FOLLOW_BATCH_MAX}. e.g. "deel.com,notion.so,hubspot.com"`,
28465
+ required: true
28466
+ },
28467
+ platform: {
28468
+ type: "string",
28469
+ description: "meta|linkedin \u2014 how to read each input, not a tracking limit (default meta). Domains resolve both.",
28470
+ required: false,
28471
+ default: "meta"
28472
+ }
28473
+ }
28474
+ });
28475
+ var followCompetitorsCommand = defineCommand167({
28476
+ meta: {
28477
+ name: "follow-competitors",
28478
+ description: 'Follow many brands at once by domain \u2014 add every competitor in one call. Example: baker winning-ads follow-competitors "deel.com,notion.so,hubspot.com"'
28479
+ },
28480
+ args: {
28481
+ domains: {
28482
+ type: "positional",
28483
+ description: "Comma-separated domains (best), URLs, or brand names",
28484
+ required: true
28485
+ },
28486
+ platform: {
28487
+ type: "string",
28488
+ description: "meta|linkedin \u2014 how to read each input, not a tracking limit (default meta).",
28489
+ required: false,
28490
+ default: "meta"
28491
+ }
28492
+ },
28493
+ run: async ({ args }) => {
28494
+ try {
28495
+ const platform = String(args.platform ?? "meta");
28496
+ if (!PLATFORMS2.includes(platform)) {
28497
+ writeJson({
28498
+ ok: false,
28499
+ error: { code: "VALIDATION_ERROR", message: "--platform must be one of: meta, linkedin" }
28500
+ });
28501
+ process.exit(1);
28502
+ }
28503
+ const body = buildFollowBatchBody({ domains: String(args.domains ?? ""), platform });
28504
+ if (body.inputs.length === 0) {
28505
+ writeJson({
28506
+ ok: false,
28507
+ error: { code: "VALIDATION_ERROR", message: "Pass at least one domain in --domains." }
28508
+ });
28509
+ process.exit(1);
28510
+ }
28511
+ if (body.inputs.length > FOLLOW_BATCH_MAX) {
28512
+ writeJson({
28513
+ ok: false,
28514
+ error: {
28515
+ code: "VALIDATION_ERROR",
28516
+ message: `Too many brands (${body.inputs.length}). Follow at most ${FOLLOW_BATCH_MAX} per call.`
28517
+ }
28518
+ });
28519
+ process.exit(1);
28520
+ }
28521
+ const data = await apiPost("/api/ad-library/follow-batch", body, {
28522
+ timeoutMs: BATCH_TIMEOUT_MS
28523
+ });
28524
+ const hints = [];
28525
+ if (data.failed_count > 0) {
28526
+ const failed = data.results.filter((r) => r.status === "failed").map((r) => r.input);
28527
+ hints.push(
28528
+ `${data.failed_count} brand(s) couldn't be added: ${failed.join(", ")}. Retry each with its website or Facebook/LinkedIn URL via \`baker winning-ads follow\`.`
28529
+ );
28530
+ }
28531
+ if (data.ambiguous_count > 0) {
28532
+ const ambiguous = data.results.filter((r) => r.status === "ambiguous").map((r) => r.input);
28533
+ hints.push(
28534
+ `${data.ambiguous_count} brand(s) matched multiple advertisers: ${ambiguous.join(", ")}. Re-run \`baker winning-ads follow\` with an exact domain/URL to pick one.`
28535
+ );
28536
+ }
28537
+ if (data.added_count > 0) {
28538
+ hints.push(
28539
+ "Newly added brands are ingesting now \u2014 their winners fill in over the next few minutes. Check `baker winning-ads following` or `baker winning-ads feed`."
28540
+ );
28541
+ }
28542
+ writeJson({ ok: true, data, hints: hints.length > 0 ? hints : void 0 });
28543
+ } catch (err) {
28544
+ reportError(err);
28545
+ }
28546
+ }
28547
+ });
28548
+
28549
+ // src/commands/winning-ads/following.ts
28550
+ import { defineCommand as defineCommand168 } from "citty";
28393
28551
  registerSchema({
28394
28552
  command: "winning-ads.following",
28395
28553
  description: "List the brands you follow in your ad-dna library, with each one's status (ready vs still adding) and cached ad counts.",
@@ -28422,7 +28580,7 @@ function followingNormalizer(record, full) {
28422
28580
  platforms: Array.isArray(record.platforms) ? record.platforms : []
28423
28581
  };
28424
28582
  }
28425
- var followingCommand = defineCommand167({
28583
+ var followingCommand = defineCommand168({
28426
28584
  meta: {
28427
28585
  name: "following",
28428
28586
  description: "List brands you follow, with status (ready / adding\u2026) and cached counts. Example: baker winning-ads following --output md"
@@ -28457,7 +28615,7 @@ var followingCommand = defineCommand167({
28457
28615
  });
28458
28616
 
28459
28617
  // src/commands/winning-ads/patterns.ts
28460
- import { defineCommand as defineCommand168 } from "citty";
28618
+ import { defineCommand as defineCommand169 } from "citty";
28461
28619
  registerSchema({
28462
28620
  command: "winning-ads.patterns",
28463
28621
  description: "Mine what separates two cohorts of ads: pass a comma-list of winning ad ids (--winners) and a comma-list of weaker ad ids (--duds). Returns the discriminating DNA fields.",
@@ -28496,7 +28654,7 @@ function discriminatorRow(record) {
28496
28654
  top_values_duds: Array.isArray(record.top_values_b) ? record.top_values_b.join(", ") : ""
28497
28655
  };
28498
28656
  }
28499
- var patternsCommand = defineCommand168({
28657
+ var patternsCommand = defineCommand169({
28500
28658
  meta: {
28501
28659
  name: "patterns",
28502
28660
  description: "Discover what separates winning ads from weak ones. Example: baker winning-ads patterns --winners a_1,a_2,a_3 --duds a_9,a_8 --output md"
@@ -28552,7 +28710,7 @@ var patternsCommand = defineCommand168({
28552
28710
  });
28553
28711
 
28554
28712
  // src/commands/winning-ads/search.ts
28555
- import { defineCommand as defineCommand169 } from "citty";
28713
+ import { defineCommand as defineCommand170 } from "citty";
28556
28714
  registerSchema({
28557
28715
  command: "winning-ads.search",
28558
28716
  description: "Search the ad-dna corpus of scored winning ads. Returns a lean shortlist (advertiser, summary, scores, media_url) to pick a reference to reproduce.",
@@ -28660,7 +28818,7 @@ function buildSearchBody(args) {
28660
28818
  }
28661
28819
  return body;
28662
28820
  }
28663
- var searchCommand4 = defineCommand169({
28821
+ var searchCommand4 = defineCommand170({
28664
28822
  meta: {
28665
28823
  name: "search",
28666
28824
  description: "Search winning reference ads. Example: baker winning-ads search 'B2B SaaS before/after AI automation' --platform meta --format static --winner-category winner --exclude-advertiser adv_123 --output md"
@@ -28775,7 +28933,7 @@ var searchCommand4 = defineCommand169({
28775
28933
  });
28776
28934
 
28777
28935
  // src/commands/winning-ads/seeds.ts
28778
- import { defineCommand as defineCommand170 } from "citty";
28936
+ import { defineCommand as defineCommand171 } from "citty";
28779
28937
  function leanRow(r) {
28780
28938
  return {
28781
28939
  key: r.key,
@@ -28803,7 +28961,7 @@ function makeSeedCommand(opts) {
28803
28961
  limit: { type: "number", description: "Max keys 1-100 (default 20)", required: false, default: 20 }
28804
28962
  }
28805
28963
  });
28806
- return defineCommand170({
28964
+ return defineCommand171({
28807
28965
  meta: { name: opts.name, description: opts.description },
28808
28966
  args: {
28809
28967
  platform: { type: "string", description: "Single platform to segment on", required: false },
@@ -28852,7 +29010,7 @@ var formatsCommand = makeSeedCommand({
28852
29010
  });
28853
29011
 
28854
29012
  // src/commands/winning-ads/unfollow.ts
28855
- import { defineCommand as defineCommand171 } from "citty";
29013
+ import { defineCommand as defineCommand172 } from "citty";
28856
29014
  registerSchema({
28857
29015
  command: "winning-ads.unfollow",
28858
29016
  description: "Stop following a brand \u2014 removes it from your ad-dna library by advertiser id.",
@@ -28860,7 +29018,7 @@ registerSchema({
28860
29018
  advertiser: { type: "string", description: "Advertiser id to unfollow", required: true }
28861
29019
  }
28862
29020
  });
28863
- var unfollowCommand = defineCommand171({
29021
+ var unfollowCommand = defineCommand172({
28864
29022
  meta: {
28865
29023
  name: "unfollow",
28866
29024
  description: "Stop following a brand by advertiser id. Example: baker winning-ads unfollow adv_123"
@@ -28881,7 +29039,7 @@ var unfollowCommand = defineCommand171({
28881
29039
  });
28882
29040
 
28883
29041
  // src/commands/winning-ads/winners.ts
28884
- import { defineCommand as defineCommand172 } from "citty";
29042
+ import { defineCommand as defineCommand173 } from "citty";
28885
29043
  registerSchema({
28886
29044
  command: "winning-ads.winners",
28887
29045
  description: "Top winning ads for one advertiser id (from `advertisers` or `following`). Returns lean winner cards; add --full for DNA + longevity.",
@@ -28891,7 +29049,7 @@ registerSchema({
28891
29049
  platform: { type: "string", description: "Filter to a single platform: meta|linkedin", required: false }
28892
29050
  }
28893
29051
  });
28894
- var winnersCommand = defineCommand172({
29052
+ var winnersCommand = defineCommand173({
28895
29053
  meta: {
28896
29054
  name: "winners",
28897
29055
  description: "Top winning ads for a specific advertiser id. Example: baker winning-ads winners adv_123 --top 15 --output md"
@@ -28941,7 +29099,7 @@ var winnersCommand = defineCommand172({
28941
29099
  });
28942
29100
 
28943
29101
  // src/commands/winning-ads/index.ts
28944
- var winningAdsCommand = defineCommand173({
29102
+ var winningAdsCommand = defineCommand174({
28945
29103
  meta: {
28946
29104
  name: "winning-ads",
28947
29105
  description: `Search the ad-dna corpus of scored "winning" ads for reference creatives to reproduce, and manage the brands your library tracks. Proxied through the Baker backend (BAKER_API_KEY) \u2014 no separate token needed.
@@ -28952,6 +29110,7 @@ Subcommands:
28952
29110
  baker winning-ads search "<free text>" \u2014 semantic search across the corpus; lean shortlist (advertiser, summary, scores, media_url)
28953
29111
  baker winning-ads advertisers "<brand>" \u2014 list corpus brands \u2192 advertiser ids (for --exclude-advertiser / --advertiser-id)
28954
29112
  baker winning-ads follow "<domain|url|brand>" --platform meta|linkedin \u2014 add a brand's ads to your library
29113
+ baker winning-ads follow-competitors "<domain,domain,...>" \u2014 add MANY brands at once (every competitor by domain)
28955
29114
  baker winning-ads following \u2014 list brands you follow (status + counts)
28956
29115
  baker winning-ads feed \u2014 winners across EVERY brand you follow; trim with --advertiser
28957
29116
  baker winning-ads winners <advertiser> \u2014 top winners for one advertiser id
@@ -28966,6 +29125,7 @@ Examples:
28966
29125
  baker winning-ads search "B2B SaaS before/after AI automation" --platform meta --format static --output md
28967
29126
  baker winning-ads advertisers "Acme" --output md
28968
29127
  baker winning-ads follow "deel.com" --platform meta
29128
+ baker winning-ads follow-competitors "deel.com,notion.so,hubspot.com"
28969
29129
  baker winning-ads feed --per-advertiser 5 --output md
28970
29130
  baker winning-ads feed --advertiser adv_123,adv_456 --platform meta --output md
28971
29131
  baker winning-ads winners adv_123 --top 15 --output md
@@ -28977,6 +29137,7 @@ Examples:
28977
29137
  search: searchCommand4,
28978
29138
  advertisers: advertisersCommand2,
28979
29139
  follow: followCommand,
29140
+ "follow-competitors": followCompetitorsCommand,
28980
29141
  following: followingCommand,
28981
29142
  feed: feedCommand,
28982
29143
  winners: winnersCommand,
@@ -29006,7 +29167,7 @@ function getCliVersion() {
29006
29167
  }
29007
29168
 
29008
29169
  // src/cli.ts
29009
- var main = defineCommand174({
29170
+ var main = defineCommand175({
29010
29171
  meta: {
29011
29172
  name: "baker",
29012
29173
  version: getCliVersion(),