@koda-sl/baker-cli 0.133.3 → 0.134.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
@@ -2185,7 +2185,7 @@ baker testimonials tags
2185
2185
 
2186
2186
  ### Winning Ads (`baker winning-ads`)
2187
2187
 
2188
- Search the **ad-dna** corpus of scored "winning" competitor ads for reference creatives to reproduce (e.g. with `baker canvas`), and manage the brands your library tracks (`follow` / `following` / `unfollow`). Each result carries a presigned media URL (~1h TTL), the ad's DNA summary, and scores. The CLI authenticates with the normal `BAKER_API_KEY`; the Baker backend proxies the request to the ad-dna service with a server-held token — no extra credential in the sandbox.
2188
+ Search the **ad-dna** corpus of scored "winning" competitor ads for reference creatives to reproduce (e.g. with `baker canvas`), and manage the brands your library tracks (`follow` / `following` / `feed` / `unfollow`). Each result carries a presigned media URL (~1h TTL), the ad's DNA summary, and scores. The CLI authenticates with the normal `BAKER_API_KEY`; the Baker backend proxies the request to the ad-dna service with a server-held token — no extra credential in the sandbox.
2189
2189
 
2190
2190
  > The corpus has **Meta + LinkedIn** connectors, so `--platform` inputs are limited to `meta,linkedin`. (Older result rows may still carry a legacy platform string.)
2191
2191
 
@@ -2289,9 +2289,29 @@ List the brands you follow → `GET /api/ad-library/following`. Each row shows `
2289
2289
  baker winning-ads following --output md
2290
2290
  ```
2291
2291
 
2292
+ ### `baker winning-ads feed`
2293
+
2294
+ Winners across **every** brand you follow, in one library → `GET /api/ad-library/feed`. The advertiser set is derived server-side from your follows (never a client-supplied list), so this is the "what's winning across everyone I track" view. Start here, then trim to specific brands with `--advertiser adv_1,adv_2`. Capped per advertiser (`--per-advertiser`, default 5, `0` = no cap) so no single brand floods the feed — the cap is enforced server-side, so it holds even when you narrow to one advertiser. Same lean winner cards as `search` / `winners` (add `--full` for DNA + longevity).
2295
+
2296
+ Response `data`: `winners`, `following_count` (how many brands you follow), `advertiser_ids` (the ids actually queried = follows ∩ requested), and — when relevant — `hints` (e.g. you follow nobody yet, or a requested id isn't in your follows and was skipped). In `--output md`/`files` the table is the payload and the hints are written to **stderr** so they still surface without corrupting the output.
2297
+
2298
+ ```bash
2299
+ baker winning-ads feed --per-advertiser 5 --output md
2300
+ baker winning-ads feed --advertiser adv_123,adv_456 --platform meta --output md
2301
+ ```
2302
+
2303
+ | Flag | Purpose |
2304
+ |---|---|
2305
+ | `--advertiser <ids>` | Trim to a comma-separated subset of your followed advertiser ids (ids you don't follow are skipped and reported) |
2306
+ | `--platform meta\|linkedin` | Filter to a single platform |
2307
+ | `--per-advertiser <n>` | Max winners kept per advertiser 0–50 (`0` = no cap, default 5) |
2308
+ | `--limit <n>` | Total winners across all follows 1–200 (default 40) |
2309
+ | `--winner-category <list>` | Restrict to specific winner categories |
2310
+ | `--format <list>` | Restrict to specific formats (e.g. `static,video`) |
2311
+
2292
2312
  ### `baker winning-ads winners <advertiser>`
2293
2313
 
2294
- Top winning ads for one advertiser id → `GET /api/ad-library/advertiser-winners`. Same lean winner cards as `search` (add `--full` for DNA + longevity). Supports `--top N` and `--platform meta|linkedin`.
2314
+ Top winning ads for one advertiser id → `GET /api/ad-library/advertiser-winners` (use `feed` for all your follows at once). Same lean winner cards as `search` (add `--full` for DNA + longevity). Supports `--top N` and `--platform meta|linkedin`.
2295
2315
 
2296
2316
  ```bash
2297
2317
  baker winning-ads winners adv_123 --top 15 --output md
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 defineCommand171, runMain } from "citty";
76
+ import { defineCommand as defineCommand172, runMain } from "citty";
77
77
 
78
78
  // src/commands/actions/index.ts
79
79
  import { defineCommand as defineCommand18 } from "citty";
@@ -1023,6 +1023,30 @@ var advertiserWinnersResponseSchema = z2.object({
1023
1023
  advertiser_id: z2.string(),
1024
1024
  winners: z2.array(winningAdSchema)
1025
1025
  });
1026
+ var feedRequestSchema = z2.object({
1027
+ // Optional subset of your followed advertiser ids to narrow to ("trim down
1028
+ // per advertiser"). Ids you don't follow are ignored and echoed back in
1029
+ // `unknown_requested`. Omit to browse winners across every brand you follow.
1030
+ advertiser_ids: z2.array(z2.string()).optional(),
1031
+ platform: adLibraryPlatformSchema.optional(),
1032
+ winner_category: z2.array(z2.string()).optional(),
1033
+ format: z2.array(z2.string()).optional(),
1034
+ // Winners kept per advertiser so no single brand floods the feed. `0` = no
1035
+ // cap. Default applied by the route.
1036
+ per_advertiser: z2.number().int().min(0).max(50).optional(),
1037
+ // Total winners returned across all followed advertisers.
1038
+ limit: z2.number().int().min(1).max(200).optional()
1039
+ });
1040
+ var feedResponseSchema = z2.object({
1041
+ winners: z2.array(winningAdSchema),
1042
+ // How many brands the company follows (regardless of the trim).
1043
+ following_count: z2.number(),
1044
+ // The advertiser ids actually queried (follows ∩ requested subset).
1045
+ advertiser_ids: z2.array(z2.string()),
1046
+ // Requested ids that aren't followed — a hint the caller mistyped or hasn't
1047
+ // followed them yet.
1048
+ unknown_requested: z2.array(z2.string())
1049
+ });
1026
1050
  var briefRequestSchema = z2.object({
1027
1051
  // Partial DNA describing the target creative (all optional); the service
1028
1052
  // pulls its own strategically-similar references.
@@ -27258,7 +27282,7 @@ Examples:
27258
27282
  });
27259
27283
 
27260
27284
  // src/commands/winning-ads/index.ts
27261
- import { defineCommand as defineCommand170 } from "citty";
27285
+ import { defineCommand as defineCommand171 } from "citty";
27262
27286
 
27263
27287
  // src/commands/winning-ads/advertisers.ts
27264
27288
  import { defineCommand as defineCommand161 } from "citty";
@@ -27453,8 +27477,145 @@ var briefCommand = defineCommand162({
27453
27477
  }
27454
27478
  });
27455
27479
 
27456
- // src/commands/winning-ads/follow.ts
27480
+ // src/commands/winning-ads/feed.ts
27457
27481
  import { defineCommand as defineCommand163 } from "citty";
27482
+ function buildFeedParams(input) {
27483
+ const params = {};
27484
+ const advertiser = splitList(input.advertiser);
27485
+ if (advertiser.length > 0) {
27486
+ params.advertiser = advertiser.join(",");
27487
+ }
27488
+ if (input.platform) {
27489
+ params.platform = input.platform;
27490
+ }
27491
+ if (input.perAdvertiser !== void 0 && input.perAdvertiser !== "") {
27492
+ params.per_advertiser = input.perAdvertiser;
27493
+ }
27494
+ if (input.limit !== void 0 && input.limit !== "") {
27495
+ params.limit = input.limit;
27496
+ }
27497
+ const winnerCategory = splitList(input.winnerCategory);
27498
+ if (winnerCategory.length > 0) {
27499
+ params.winner_category = winnerCategory.join(",");
27500
+ }
27501
+ const format = splitList(input.format);
27502
+ if (format.length > 0) {
27503
+ params.format = format.join(",");
27504
+ }
27505
+ return params;
27506
+ }
27507
+ registerSchema({
27508
+ command: "winning-ads.feed",
27509
+ description: "Winners across EVERY brand you follow, in one library. Start here to review your followed advertisers, then trim to specific ones with --advertiser. Capped per advertiser so no single brand floods the feed. Lean winner cards; add --full for DNA + longevity.",
27510
+ args: {
27511
+ advertiser: {
27512
+ type: "string",
27513
+ description: "Comma-separated followed advertiser ids to narrow to (from `following`). Omit for all follows.",
27514
+ required: false
27515
+ },
27516
+ platform: { type: "string", description: "Filter to a single platform: meta|linkedin", required: false },
27517
+ "per-advertiser": {
27518
+ type: "number",
27519
+ description: "Max winners kept per advertiser 0-50 (0 = no cap, default 5)",
27520
+ required: false,
27521
+ default: 5
27522
+ },
27523
+ limit: {
27524
+ type: "number",
27525
+ description: "Total winners across all follows 1-200 (default 40)",
27526
+ required: false,
27527
+ default: 40
27528
+ },
27529
+ "winner-category": { type: "string", description: "Comma-separated winner categories to include", required: false },
27530
+ format: { type: "string", description: "Comma-separated formats to include (e.g. static,video)", required: false }
27531
+ }
27532
+ });
27533
+ var feedCommand = defineCommand163({
27534
+ meta: {
27535
+ name: "feed",
27536
+ description: "Winners across every brand you follow (browse, then trim per advertiser). Example: baker winning-ads feed --per-advertiser 5 --output md"
27537
+ },
27538
+ args: {
27539
+ advertiser: {
27540
+ type: "string",
27541
+ description: "Comma-separated followed advertiser ids to narrow to",
27542
+ required: false
27543
+ },
27544
+ platform: { type: "string", description: "Filter to a single platform: meta|linkedin", required: false },
27545
+ "per-advertiser": {
27546
+ type: "string",
27547
+ description: "Max winners per advertiser 0-50 (0 = no cap, default 5)",
27548
+ required: false
27549
+ },
27550
+ limit: { type: "string", description: "Total winners 1-200 (default 40)", required: false },
27551
+ "winner-category": { type: "string", description: "Comma-separated winner categories", required: false },
27552
+ format: { type: "string", description: "Comma-separated formats", required: false },
27553
+ output: { type: "string", description: "Output format: json|files|md", required: false, default: "json" },
27554
+ fields: { type: "string", description: "Comma-separated field names to include", required: false },
27555
+ full: {
27556
+ type: "boolean",
27557
+ description: "Include DNA detail (angle, persona, hook) + longevity",
27558
+ required: false,
27559
+ default: false
27560
+ }
27561
+ },
27562
+ run: async ({ args }) => {
27563
+ try {
27564
+ const params = buildFeedParams({
27565
+ advertiser: args.advertiser,
27566
+ platform: args.platform,
27567
+ perAdvertiser: args["per-advertiser"],
27568
+ limit: args.limit,
27569
+ winnerCategory: args["winner-category"],
27570
+ format: args.format
27571
+ });
27572
+ const data = await apiGet("/api/ad-library/feed", params);
27573
+ const output = args.output || "json";
27574
+ const full = args.full;
27575
+ const rawWinners = Array.isArray(data?.winners) ? data.winners : [];
27576
+ const hints = [];
27577
+ if ((data?.following_count ?? 0) === 0) {
27578
+ hints.push(
27579
+ 'You don\'t follow any brands yet. Run `baker winning-ads follow "<domain>" --platform meta` first.'
27580
+ );
27581
+ }
27582
+ if (Array.isArray(data?.unknown_requested) && data.unknown_requested.length > 0) {
27583
+ hints.push(
27584
+ `Not in your follows (skipped): ${data.unknown_requested.join(", ")}. Check ids with \`baker winning-ads following\`.`
27585
+ );
27586
+ }
27587
+ if (output === "json") {
27588
+ const winners = rawWinners.map((w) => winningAdNormalizer(w, full));
27589
+ writeJson({
27590
+ ok: true,
27591
+ data: {
27592
+ winners,
27593
+ following_count: data?.following_count ?? 0,
27594
+ advertiser_ids: Array.isArray(data?.advertiser_ids) ? data.advertiser_ids : [],
27595
+ ...hints.length > 0 ? { hints } : {}
27596
+ }
27597
+ });
27598
+ return;
27599
+ }
27600
+ writeOutput(
27601
+ { ok: true, data: rawWinners },
27602
+ output,
27603
+ args.fields ? args.fields.split(",") : void 0,
27604
+ full,
27605
+ winningAdNormalizer
27606
+ );
27607
+ for (const hint of hints) {
27608
+ process.stderr.write(`${hint}
27609
+ `);
27610
+ }
27611
+ } catch (err) {
27612
+ reportError(err);
27613
+ }
27614
+ }
27615
+ });
27616
+
27617
+ // src/commands/winning-ads/follow.ts
27618
+ import { defineCommand as defineCommand164 } from "citty";
27458
27619
  var PLATFORMS = ["meta", "linkedin"];
27459
27620
  registerSchema({
27460
27621
  command: "winning-ads.follow",
@@ -27469,7 +27630,7 @@ registerSchema({
27469
27630
  label: { type: "string", description: "Optional display label (defaults to the resolved name)", required: false }
27470
27631
  }
27471
27632
  });
27472
- var followCommand = defineCommand163({
27633
+ var followCommand = defineCommand164({
27473
27634
  meta: {
27474
27635
  name: "follow",
27475
27636
  description: 'Follow a brand to track ALL its ads \u2014 every platform and country. --platform is how we read your input, not a limit. A domain tracks both Meta + LinkedIn. Example: baker winning-ads follow "deel.com" --platform meta'
@@ -27516,7 +27677,7 @@ var followCommand = defineCommand163({
27516
27677
  });
27517
27678
 
27518
27679
  // src/commands/winning-ads/following.ts
27519
- import { defineCommand as defineCommand164 } from "citty";
27680
+ import { defineCommand as defineCommand165 } from "citty";
27520
27681
  registerSchema({
27521
27682
  command: "winning-ads.following",
27522
27683
  description: "List the brands you follow in your ad-dna library, with each one's status (ready vs still adding) and cached ad counts.",
@@ -27549,7 +27710,7 @@ function followingNormalizer(record, full) {
27549
27710
  platforms: Array.isArray(record.platforms) ? record.platforms : []
27550
27711
  };
27551
27712
  }
27552
- var followingCommand = defineCommand164({
27713
+ var followingCommand = defineCommand165({
27553
27714
  meta: {
27554
27715
  name: "following",
27555
27716
  description: "List brands you follow, with status (ready / adding\u2026) and cached counts. Example: baker winning-ads following --output md"
@@ -27584,7 +27745,7 @@ var followingCommand = defineCommand164({
27584
27745
  });
27585
27746
 
27586
27747
  // src/commands/winning-ads/patterns.ts
27587
- import { defineCommand as defineCommand165 } from "citty";
27748
+ import { defineCommand as defineCommand166 } from "citty";
27588
27749
  registerSchema({
27589
27750
  command: "winning-ads.patterns",
27590
27751
  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.",
@@ -27623,7 +27784,7 @@ function discriminatorRow(record) {
27623
27784
  top_values_duds: Array.isArray(record.top_values_b) ? record.top_values_b.join(", ") : ""
27624
27785
  };
27625
27786
  }
27626
- var patternsCommand = defineCommand165({
27787
+ var patternsCommand = defineCommand166({
27627
27788
  meta: {
27628
27789
  name: "patterns",
27629
27790
  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"
@@ -27679,7 +27840,7 @@ var patternsCommand = defineCommand165({
27679
27840
  });
27680
27841
 
27681
27842
  // src/commands/winning-ads/search.ts
27682
- import { defineCommand as defineCommand166 } from "citty";
27843
+ import { defineCommand as defineCommand167 } from "citty";
27683
27844
  registerSchema({
27684
27845
  command: "winning-ads.search",
27685
27846
  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.",
@@ -27787,7 +27948,7 @@ function buildSearchBody(args) {
27787
27948
  }
27788
27949
  return body;
27789
27950
  }
27790
- var searchCommand4 = defineCommand166({
27951
+ var searchCommand4 = defineCommand167({
27791
27952
  meta: {
27792
27953
  name: "search",
27793
27954
  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"
@@ -27902,7 +28063,7 @@ var searchCommand4 = defineCommand166({
27902
28063
  });
27903
28064
 
27904
28065
  // src/commands/winning-ads/seeds.ts
27905
- import { defineCommand as defineCommand167 } from "citty";
28066
+ import { defineCommand as defineCommand168 } from "citty";
27906
28067
  function leanRow(r) {
27907
28068
  return {
27908
28069
  key: r.key,
@@ -27930,7 +28091,7 @@ function makeSeedCommand(opts) {
27930
28091
  limit: { type: "number", description: "Max keys 1-100 (default 20)", required: false, default: 20 }
27931
28092
  }
27932
28093
  });
27933
- return defineCommand167({
28094
+ return defineCommand168({
27934
28095
  meta: { name: opts.name, description: opts.description },
27935
28096
  args: {
27936
28097
  platform: { type: "string", description: "Single platform to segment on", required: false },
@@ -27979,7 +28140,7 @@ var formatsCommand = makeSeedCommand({
27979
28140
  });
27980
28141
 
27981
28142
  // src/commands/winning-ads/unfollow.ts
27982
- import { defineCommand as defineCommand168 } from "citty";
28143
+ import { defineCommand as defineCommand169 } from "citty";
27983
28144
  registerSchema({
27984
28145
  command: "winning-ads.unfollow",
27985
28146
  description: "Stop following a brand \u2014 removes it from your ad-dna library by advertiser id.",
@@ -27987,7 +28148,7 @@ registerSchema({
27987
28148
  advertiser: { type: "string", description: "Advertiser id to unfollow", required: true }
27988
28149
  }
27989
28150
  });
27990
- var unfollowCommand = defineCommand168({
28151
+ var unfollowCommand = defineCommand169({
27991
28152
  meta: {
27992
28153
  name: "unfollow",
27993
28154
  description: "Stop following a brand by advertiser id. Example: baker winning-ads unfollow adv_123"
@@ -28008,7 +28169,7 @@ var unfollowCommand = defineCommand168({
28008
28169
  });
28009
28170
 
28010
28171
  // src/commands/winning-ads/winners.ts
28011
- import { defineCommand as defineCommand169 } from "citty";
28172
+ import { defineCommand as defineCommand170 } from "citty";
28012
28173
  registerSchema({
28013
28174
  command: "winning-ads.winners",
28014
28175
  description: "Top winning ads for one advertiser id (from `advertisers` or `following`). Returns lean winner cards; add --full for DNA + longevity.",
@@ -28018,7 +28179,7 @@ registerSchema({
28018
28179
  platform: { type: "string", description: "Filter to a single platform: meta|linkedin", required: false }
28019
28180
  }
28020
28181
  });
28021
- var winnersCommand = defineCommand169({
28182
+ var winnersCommand = defineCommand170({
28022
28183
  meta: {
28023
28184
  name: "winners",
28024
28185
  description: "Top winning ads for a specific advertiser id. Example: baker winning-ads winners adv_123 --top 15 --output md"
@@ -28068,7 +28229,7 @@ var winnersCommand = defineCommand169({
28068
28229
  });
28069
28230
 
28070
28231
  // src/commands/winning-ads/index.ts
28071
- var winningAdsCommand = defineCommand170({
28232
+ var winningAdsCommand = defineCommand171({
28072
28233
  meta: {
28073
28234
  name: "winning-ads",
28074
28235
  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.
@@ -28080,6 +28241,7 @@ Subcommands:
28080
28241
  baker winning-ads advertisers "<brand>" \u2014 list corpus brands \u2192 advertiser ids (for --exclude-advertiser / --advertiser-id)
28081
28242
  baker winning-ads follow "<domain|url|brand>" --platform meta|linkedin \u2014 add a brand's ads to your library
28082
28243
  baker winning-ads following \u2014 list brands you follow (status + counts)
28244
+ baker winning-ads feed \u2014 winners across EVERY brand you follow; trim with --advertiser
28083
28245
  baker winning-ads winners <advertiser> \u2014 top winners for one advertiser id
28084
28246
  baker winning-ads unfollow <advertiser> \u2014 stop following a brand
28085
28247
  baker winning-ads brief \u2014 creative brief grounded in similar winners
@@ -28092,6 +28254,8 @@ Examples:
28092
28254
  baker winning-ads search "B2B SaaS before/after AI automation" --platform meta --format static --output md
28093
28255
  baker winning-ads advertisers "Acme" --output md
28094
28256
  baker winning-ads follow "deel.com" --platform meta
28257
+ baker winning-ads feed --per-advertiser 5 --output md
28258
+ baker winning-ads feed --advertiser adv_123,adv_456 --platform meta --output md
28095
28259
  baker winning-ads winners adv_123 --top 15 --output md
28096
28260
  baker winning-ads hooks --platform meta --awareness problem_aware --industry saas --output md
28097
28261
  baker winning-ads search "fintech onboarding" --hook-archetype callout --winner-category winner --output md
@@ -28102,6 +28266,7 @@ Examples:
28102
28266
  advertisers: advertisersCommand2,
28103
28267
  follow: followCommand,
28104
28268
  following: followingCommand,
28269
+ feed: feedCommand,
28105
28270
  winners: winnersCommand,
28106
28271
  unfollow: unfollowCommand,
28107
28272
  brief: briefCommand,
@@ -28129,7 +28294,7 @@ function getCliVersion() {
28129
28294
  }
28130
28295
 
28131
28296
  // src/cli.ts
28132
- var main = defineCommand171({
28297
+ var main = defineCommand172({
28133
28298
  meta: {
28134
28299
  name: "baker",
28135
28300
  version: getCliVersion(),