@koda-sl/baker-cli 0.139.1 → 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/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(),
@@ -28017,7 +28044,7 @@ Examples:
28017
28044
  });
28018
28045
 
28019
28046
  // src/commands/winning-ads/index.ts
28020
- import { defineCommand as defineCommand173 } from "citty";
28047
+ import { defineCommand as defineCommand174 } from "citty";
28021
28048
 
28022
28049
  // src/commands/winning-ads/advertisers.ts
28023
28050
  import { defineCommand as defineCommand163 } from "citty";
@@ -28411,8 +28438,116 @@ var followCommand = defineCommand166({
28411
28438
  }
28412
28439
  });
28413
28440
 
28414
- // src/commands/winning-ads/following.ts
28441
+ // src/commands/winning-ads/follow-competitors.ts
28415
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";
28416
28551
  registerSchema({
28417
28552
  command: "winning-ads.following",
28418
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.",
@@ -28445,7 +28580,7 @@ function followingNormalizer(record, full) {
28445
28580
  platforms: Array.isArray(record.platforms) ? record.platforms : []
28446
28581
  };
28447
28582
  }
28448
- var followingCommand = defineCommand167({
28583
+ var followingCommand = defineCommand168({
28449
28584
  meta: {
28450
28585
  name: "following",
28451
28586
  description: "List brands you follow, with status (ready / adding\u2026) and cached counts. Example: baker winning-ads following --output md"
@@ -28480,7 +28615,7 @@ var followingCommand = defineCommand167({
28480
28615
  });
28481
28616
 
28482
28617
  // src/commands/winning-ads/patterns.ts
28483
- import { defineCommand as defineCommand168 } from "citty";
28618
+ import { defineCommand as defineCommand169 } from "citty";
28484
28619
  registerSchema({
28485
28620
  command: "winning-ads.patterns",
28486
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.",
@@ -28519,7 +28654,7 @@ function discriminatorRow(record) {
28519
28654
  top_values_duds: Array.isArray(record.top_values_b) ? record.top_values_b.join(", ") : ""
28520
28655
  };
28521
28656
  }
28522
- var patternsCommand = defineCommand168({
28657
+ var patternsCommand = defineCommand169({
28523
28658
  meta: {
28524
28659
  name: "patterns",
28525
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"
@@ -28575,7 +28710,7 @@ var patternsCommand = defineCommand168({
28575
28710
  });
28576
28711
 
28577
28712
  // src/commands/winning-ads/search.ts
28578
- import { defineCommand as defineCommand169 } from "citty";
28713
+ import { defineCommand as defineCommand170 } from "citty";
28579
28714
  registerSchema({
28580
28715
  command: "winning-ads.search",
28581
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.",
@@ -28683,7 +28818,7 @@ function buildSearchBody(args) {
28683
28818
  }
28684
28819
  return body;
28685
28820
  }
28686
- var searchCommand4 = defineCommand169({
28821
+ var searchCommand4 = defineCommand170({
28687
28822
  meta: {
28688
28823
  name: "search",
28689
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"
@@ -28798,7 +28933,7 @@ var searchCommand4 = defineCommand169({
28798
28933
  });
28799
28934
 
28800
28935
  // src/commands/winning-ads/seeds.ts
28801
- import { defineCommand as defineCommand170 } from "citty";
28936
+ import { defineCommand as defineCommand171 } from "citty";
28802
28937
  function leanRow(r) {
28803
28938
  return {
28804
28939
  key: r.key,
@@ -28826,7 +28961,7 @@ function makeSeedCommand(opts) {
28826
28961
  limit: { type: "number", description: "Max keys 1-100 (default 20)", required: false, default: 20 }
28827
28962
  }
28828
28963
  });
28829
- return defineCommand170({
28964
+ return defineCommand171({
28830
28965
  meta: { name: opts.name, description: opts.description },
28831
28966
  args: {
28832
28967
  platform: { type: "string", description: "Single platform to segment on", required: false },
@@ -28875,7 +29010,7 @@ var formatsCommand = makeSeedCommand({
28875
29010
  });
28876
29011
 
28877
29012
  // src/commands/winning-ads/unfollow.ts
28878
- import { defineCommand as defineCommand171 } from "citty";
29013
+ import { defineCommand as defineCommand172 } from "citty";
28879
29014
  registerSchema({
28880
29015
  command: "winning-ads.unfollow",
28881
29016
  description: "Stop following a brand \u2014 removes it from your ad-dna library by advertiser id.",
@@ -28883,7 +29018,7 @@ registerSchema({
28883
29018
  advertiser: { type: "string", description: "Advertiser id to unfollow", required: true }
28884
29019
  }
28885
29020
  });
28886
- var unfollowCommand = defineCommand171({
29021
+ var unfollowCommand = defineCommand172({
28887
29022
  meta: {
28888
29023
  name: "unfollow",
28889
29024
  description: "Stop following a brand by advertiser id. Example: baker winning-ads unfollow adv_123"
@@ -28904,7 +29039,7 @@ var unfollowCommand = defineCommand171({
28904
29039
  });
28905
29040
 
28906
29041
  // src/commands/winning-ads/winners.ts
28907
- import { defineCommand as defineCommand172 } from "citty";
29042
+ import { defineCommand as defineCommand173 } from "citty";
28908
29043
  registerSchema({
28909
29044
  command: "winning-ads.winners",
28910
29045
  description: "Top winning ads for one advertiser id (from `advertisers` or `following`). Returns lean winner cards; add --full for DNA + longevity.",
@@ -28914,7 +29049,7 @@ registerSchema({
28914
29049
  platform: { type: "string", description: "Filter to a single platform: meta|linkedin", required: false }
28915
29050
  }
28916
29051
  });
28917
- var winnersCommand = defineCommand172({
29052
+ var winnersCommand = defineCommand173({
28918
29053
  meta: {
28919
29054
  name: "winners",
28920
29055
  description: "Top winning ads for a specific advertiser id. Example: baker winning-ads winners adv_123 --top 15 --output md"
@@ -28964,7 +29099,7 @@ var winnersCommand = defineCommand172({
28964
29099
  });
28965
29100
 
28966
29101
  // src/commands/winning-ads/index.ts
28967
- var winningAdsCommand = defineCommand173({
29102
+ var winningAdsCommand = defineCommand174({
28968
29103
  meta: {
28969
29104
  name: "winning-ads",
28970
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.
@@ -28975,6 +29110,7 @@ Subcommands:
28975
29110
  baker winning-ads search "<free text>" \u2014 semantic search across the corpus; lean shortlist (advertiser, summary, scores, media_url)
28976
29111
  baker winning-ads advertisers "<brand>" \u2014 list corpus brands \u2192 advertiser ids (for --exclude-advertiser / --advertiser-id)
28977
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)
28978
29114
  baker winning-ads following \u2014 list brands you follow (status + counts)
28979
29115
  baker winning-ads feed \u2014 winners across EVERY brand you follow; trim with --advertiser
28980
29116
  baker winning-ads winners <advertiser> \u2014 top winners for one advertiser id
@@ -28989,6 +29125,7 @@ Examples:
28989
29125
  baker winning-ads search "B2B SaaS before/after AI automation" --platform meta --format static --output md
28990
29126
  baker winning-ads advertisers "Acme" --output md
28991
29127
  baker winning-ads follow "deel.com" --platform meta
29128
+ baker winning-ads follow-competitors "deel.com,notion.so,hubspot.com"
28992
29129
  baker winning-ads feed --per-advertiser 5 --output md
28993
29130
  baker winning-ads feed --advertiser adv_123,adv_456 --platform meta --output md
28994
29131
  baker winning-ads winners adv_123 --top 15 --output md
@@ -29000,6 +29137,7 @@ Examples:
29000
29137
  search: searchCommand4,
29001
29138
  advertisers: advertisersCommand2,
29002
29139
  follow: followCommand,
29140
+ "follow-competitors": followCompetitorsCommand,
29003
29141
  following: followingCommand,
29004
29142
  feed: feedCommand,
29005
29143
  winners: winnersCommand,
@@ -29029,7 +29167,7 @@ function getCliVersion() {
29029
29167
  }
29030
29168
 
29031
29169
  // src/cli.ts
29032
- var main = defineCommand174({
29170
+ var main = defineCommand175({
29033
29171
  meta: {
29034
29172
  name: "baker",
29035
29173
  version: getCliVersion(),