@koda-sl/baker-cli 0.133.1 → 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/dist/cli.js CHANGED
@@ -34,7 +34,7 @@ import {
34
34
  toModelSafeImage,
35
35
  ulid,
36
36
  validateCanvasDeep
37
- } from "./chunk-Q4UI5JZN.js";
37
+ } from "./chunk-CIF62V7L.js";
38
38
  import {
39
39
  csvOrJson,
40
40
  daysAgoIso,
@@ -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.
@@ -20631,20 +20655,6 @@ function parseBudget(raw) {
20631
20655
  }
20632
20656
  function resolveModels2(args) {
20633
20657
  const pick = (flag, kind, fallback) => args[flag] ? String(args[flag]) : resolveModel2(kind, fallback);
20634
- let videoModel;
20635
- let videoRouteReason;
20636
- if (args["video-model"]) {
20637
- videoModel = resolveModel2("video_generate", String(args["video-model"]));
20638
- } else {
20639
- const route = routeVideoModel({
20640
- hasRealFace: Boolean(args["real-face"]),
20641
- needsIdentity: Boolean(args.identity),
20642
- needsMotionTransfer: Boolean(args["motion-transfer"]),
20643
- budget: parseBudget(args.budget)
20644
- });
20645
- videoModel = resolveModel2("video_generate", route.model);
20646
- videoRouteReason = route.reason;
20647
- }
20648
20658
  return {
20649
20659
  // Flash by default: ~2-3× faster per call than Pro, which keeps each
20650
20660
  // per-scene deconstruct step well inside the action time budget and turns a
@@ -20655,11 +20665,29 @@ function resolveModels2(args) {
20655
20665
  // Default to the strongest image model (matches the static-ad scaffold); the
20656
20666
  // frame generators need the most faithful text/identity reproduction. Override
20657
20667
  // with --image-model for a cheaper/faster pass.
20658
- imageModel: pick("image-model", "image_generate", "openai/gpt-5.4-image-2"),
20659
- videoModel: videoModel || DEFAULT_VIDEO_GENERATE_MODEL,
20660
- videoRouteReason
20668
+ imageModel: pick("image-model", "image_generate", "openai/gpt-5.4-image-2")
20661
20669
  };
20662
20670
  }
20671
+ function hasPhotorealCast(elements) {
20672
+ return Array.isArray(elements) && elements.some((e) => {
20673
+ const type = typeof e === "object" && e !== null ? e.type : void 0;
20674
+ return type === "person" || type === "animal";
20675
+ });
20676
+ }
20677
+ function resolveVideoModel(args, elements) {
20678
+ if (args["video-model"]) {
20679
+ return { videoModel: resolveModel2("video_generate", String(args["video-model"])) };
20680
+ }
20681
+ const photorealCast = hasPhotorealCast(elements);
20682
+ const route = routeVideoModel({
20683
+ hasRealFace: Boolean(args["real-face"]) || photorealCast,
20684
+ needsIdentity: Boolean(args.identity),
20685
+ needsMotionTransfer: Boolean(args["motion-transfer"]),
20686
+ budget: parseBudget(args.budget)
20687
+ });
20688
+ const reason = photorealCast && !args["real-face"] ? `${route.reason} \u2014 detected a photoreal on-camera cast, which Seedance's real-person filter would block` : route.reason;
20689
+ return { videoModel: resolveModel2("video_generate", route.model) || DEFAULT_VIDEO_GENERATE_MODEL, videoRouteReason: reason };
20690
+ }
20663
20691
  function buildDeconstructCanvas(videoPath, deconstructModel, opts) {
20664
20692
  const deconstructParams = { model: deconstructModel, mode: "full" };
20665
20693
  if (typeof opts.maxScenes === "number") deconstructParams.max_scenes = opts.maxScenes;
@@ -20780,7 +20808,7 @@ var scaffoldVideoCommand = defineCommand92({
20780
20808
  "video-model": { type: "string", description: "Override the video_generate model id for clips (skips the scored router)" },
20781
20809
  "real-face": {
20782
20810
  type: "boolean",
20783
- description: "Brief needs a real human likeness preserved \u2192 the router picks Veo (dodges the ByteDance real-person filter)."
20811
+ description: "The ad has a photoreal human face on camera \u2192 the router picks Veo (dodges the ByteDance real-person filter). Set this for ANY photoreal presenter/creator/testimonial cast \u2014 the filter blocks AI-GENERATED photoreal faces too, not just real ones."
20784
20812
  },
20785
20813
  "motion-transfer": {
20786
20814
  type: "boolean",
@@ -20839,7 +20867,7 @@ var scaffoldVideoCommand = defineCommand92({
20839
20867
  `
20840
20868
  );
20841
20869
  }
20842
- const { deconstructModel, selectModel, imageModel, videoModel, videoRouteReason } = resolveModels2(args);
20870
+ const { deconstructModel, selectModel, imageModel } = resolveModels2(args);
20843
20871
  const shotThreshold = args["shot-threshold"] ? Number(args["shot-threshold"]) : void 0;
20844
20872
  const shotCuts = await detectShotCutsBestEffort(videoPath, shotThreshold);
20845
20873
  const deconstructCanvas = buildDeconstructCanvas(videoPath, deconstructModel, {
@@ -20849,6 +20877,11 @@ var scaffoldVideoCommand = defineCommand92({
20849
20877
  shotCuts
20850
20878
  });
20851
20879
  const { blueprint, elements, transcript, creditsSpent } = await runAnalysisPasses(deconstructCanvas, selectModel);
20880
+ const { videoModel, videoRouteReason } = resolveVideoModel(args, elements);
20881
+ if (videoRouteReason && !args["video-model"] && !args["real-face"] && hasPhotorealCast(elements)) {
20882
+ process.stderr.write(`\u{1F3AC} Photoreal on-camera cast detected \u2192 generating clips on ${videoModel} (dodges Seedance's real-person filter).
20883
+ `);
20884
+ }
20852
20885
  await mkdir6(outDir, { recursive: true });
20853
20886
  const annotated = annotateBlueprintWithElements(blueprint, elements);
20854
20887
  await writeSceneFiles(outDir, annotated);
@@ -21018,12 +21051,12 @@ var scaffoldVideoCommand = defineCommand92({
21018
21051
  // on-brand color. The overlays inherit the reference's motion unless the
21019
21052
  // operator restyles easing/transition/accent from the brand's motion language.
21020
21053
  prompt_discipline: "Rewrite each scene with the six-decision structure (Route / Spec / Beats / Copy / Technique / Negatives): quote exact copy, give every beat a timestamp, and name the moves. Pull easing, transition (cut vs fade), pacing, and the accent moment from BRAND.md \xA7 Brand in Motion so the overlays move like THIS brand \u2014 not the reference's. If BRAND.md has no Brand-in-Motion section, seed it with brand-build first. See the creative-canvas-ads skill: references/prompt-anatomy.md + references/hyperframes/.",
21021
- // A photoreal on-camera person/animal on Seedance can trip ByteDance's
21022
- // real-person-likeness filter (422 content_policy_blocked, NON-retryable — no
21023
- // prompt reframe clears it). Surface the escape BEFORE the billed run so a
21024
- // face-heavy ad isn't discovered broken mid-render.
21054
+ // Photoreal on-camera casts auto-route to Veo above, so this only fires
21055
+ // when `--video-model bytedance/seedance-2.0` was EXPLICITLY forced despite
21056
+ // a person/animal cast overriding that safety route. Warn that every such
21057
+ // clip will hit ByteDance's real-person filter and burn a wasted attempt.
21025
21058
  ...report.elements.some((e) => e.type === "person" || e.type === "animal") && /seedance/i.test(videoModel) ? {
21026
- content_policy_risk: "This ad has a photoreal on-camera cast generating on Seedance. ByteDance's real-person-likeness filter can reject a photoreal AI face with a NON-retryable 422 (content_policy_blocked) \u2014 no prompt change clears it. If clips fail that way, regenerate on Veo (re-run with `--video-model google/veo-3.1-fast`) or make the frame less photoreal."
21059
+ content_policy_risk: "You FORCED Seedance (`--video-model bytedance/seedance-2.0`) on an ad with a photoreal on-camera cast. ByteDance's real-person filter will reject those clips (content_policy_blocked, E005) \u2014 no prompt change clears it. Each clip auto-recovers on Veo at render time so the run still completes, but you burn a wasted Seedance attempt per clip. Drop `--video-model` to let the scaffold route the cast to Veo up front, or make the frames less photorealistic."
21027
21060
  } : {},
21028
21061
  note: "Drop ONE real source image at each el_* [TODO] (reused across every frame that element appears in), confirm each voice_select casting, then `baker canvas validate` and `baker canvas run`. Running generates many billed image/video/audio assets \u2014 it is not free."
21029
21062
  }
@@ -27249,7 +27282,7 @@ Examples:
27249
27282
  });
27250
27283
 
27251
27284
  // src/commands/winning-ads/index.ts
27252
- import { defineCommand as defineCommand170 } from "citty";
27285
+ import { defineCommand as defineCommand171 } from "citty";
27253
27286
 
27254
27287
  // src/commands/winning-ads/advertisers.ts
27255
27288
  import { defineCommand as defineCommand161 } from "citty";
@@ -27444,8 +27477,145 @@ var briefCommand = defineCommand162({
27444
27477
  }
27445
27478
  });
27446
27479
 
27447
- // src/commands/winning-ads/follow.ts
27480
+ // src/commands/winning-ads/feed.ts
27448
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";
27449
27619
  var PLATFORMS = ["meta", "linkedin"];
27450
27620
  registerSchema({
27451
27621
  command: "winning-ads.follow",
@@ -27460,7 +27630,7 @@ registerSchema({
27460
27630
  label: { type: "string", description: "Optional display label (defaults to the resolved name)", required: false }
27461
27631
  }
27462
27632
  });
27463
- var followCommand = defineCommand163({
27633
+ var followCommand = defineCommand164({
27464
27634
  meta: {
27465
27635
  name: "follow",
27466
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'
@@ -27507,7 +27677,7 @@ var followCommand = defineCommand163({
27507
27677
  });
27508
27678
 
27509
27679
  // src/commands/winning-ads/following.ts
27510
- import { defineCommand as defineCommand164 } from "citty";
27680
+ import { defineCommand as defineCommand165 } from "citty";
27511
27681
  registerSchema({
27512
27682
  command: "winning-ads.following",
27513
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.",
@@ -27540,7 +27710,7 @@ function followingNormalizer(record, full) {
27540
27710
  platforms: Array.isArray(record.platforms) ? record.platforms : []
27541
27711
  };
27542
27712
  }
27543
- var followingCommand = defineCommand164({
27713
+ var followingCommand = defineCommand165({
27544
27714
  meta: {
27545
27715
  name: "following",
27546
27716
  description: "List brands you follow, with status (ready / adding\u2026) and cached counts. Example: baker winning-ads following --output md"
@@ -27575,7 +27745,7 @@ var followingCommand = defineCommand164({
27575
27745
  });
27576
27746
 
27577
27747
  // src/commands/winning-ads/patterns.ts
27578
- import { defineCommand as defineCommand165 } from "citty";
27748
+ import { defineCommand as defineCommand166 } from "citty";
27579
27749
  registerSchema({
27580
27750
  command: "winning-ads.patterns",
27581
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.",
@@ -27614,7 +27784,7 @@ function discriminatorRow(record) {
27614
27784
  top_values_duds: Array.isArray(record.top_values_b) ? record.top_values_b.join(", ") : ""
27615
27785
  };
27616
27786
  }
27617
- var patternsCommand = defineCommand165({
27787
+ var patternsCommand = defineCommand166({
27618
27788
  meta: {
27619
27789
  name: "patterns",
27620
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"
@@ -27670,7 +27840,7 @@ var patternsCommand = defineCommand165({
27670
27840
  });
27671
27841
 
27672
27842
  // src/commands/winning-ads/search.ts
27673
- import { defineCommand as defineCommand166 } from "citty";
27843
+ import { defineCommand as defineCommand167 } from "citty";
27674
27844
  registerSchema({
27675
27845
  command: "winning-ads.search",
27676
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.",
@@ -27778,7 +27948,7 @@ function buildSearchBody(args) {
27778
27948
  }
27779
27949
  return body;
27780
27950
  }
27781
- var searchCommand4 = defineCommand166({
27951
+ var searchCommand4 = defineCommand167({
27782
27952
  meta: {
27783
27953
  name: "search",
27784
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"
@@ -27893,7 +28063,7 @@ var searchCommand4 = defineCommand166({
27893
28063
  });
27894
28064
 
27895
28065
  // src/commands/winning-ads/seeds.ts
27896
- import { defineCommand as defineCommand167 } from "citty";
28066
+ import { defineCommand as defineCommand168 } from "citty";
27897
28067
  function leanRow(r) {
27898
28068
  return {
27899
28069
  key: r.key,
@@ -27921,7 +28091,7 @@ function makeSeedCommand(opts) {
27921
28091
  limit: { type: "number", description: "Max keys 1-100 (default 20)", required: false, default: 20 }
27922
28092
  }
27923
28093
  });
27924
- return defineCommand167({
28094
+ return defineCommand168({
27925
28095
  meta: { name: opts.name, description: opts.description },
27926
28096
  args: {
27927
28097
  platform: { type: "string", description: "Single platform to segment on", required: false },
@@ -27970,7 +28140,7 @@ var formatsCommand = makeSeedCommand({
27970
28140
  });
27971
28141
 
27972
28142
  // src/commands/winning-ads/unfollow.ts
27973
- import { defineCommand as defineCommand168 } from "citty";
28143
+ import { defineCommand as defineCommand169 } from "citty";
27974
28144
  registerSchema({
27975
28145
  command: "winning-ads.unfollow",
27976
28146
  description: "Stop following a brand \u2014 removes it from your ad-dna library by advertiser id.",
@@ -27978,7 +28148,7 @@ registerSchema({
27978
28148
  advertiser: { type: "string", description: "Advertiser id to unfollow", required: true }
27979
28149
  }
27980
28150
  });
27981
- var unfollowCommand = defineCommand168({
28151
+ var unfollowCommand = defineCommand169({
27982
28152
  meta: {
27983
28153
  name: "unfollow",
27984
28154
  description: "Stop following a brand by advertiser id. Example: baker winning-ads unfollow adv_123"
@@ -27999,7 +28169,7 @@ var unfollowCommand = defineCommand168({
27999
28169
  });
28000
28170
 
28001
28171
  // src/commands/winning-ads/winners.ts
28002
- import { defineCommand as defineCommand169 } from "citty";
28172
+ import { defineCommand as defineCommand170 } from "citty";
28003
28173
  registerSchema({
28004
28174
  command: "winning-ads.winners",
28005
28175
  description: "Top winning ads for one advertiser id (from `advertisers` or `following`). Returns lean winner cards; add --full for DNA + longevity.",
@@ -28009,7 +28179,7 @@ registerSchema({
28009
28179
  platform: { type: "string", description: "Filter to a single platform: meta|linkedin", required: false }
28010
28180
  }
28011
28181
  });
28012
- var winnersCommand = defineCommand169({
28182
+ var winnersCommand = defineCommand170({
28013
28183
  meta: {
28014
28184
  name: "winners",
28015
28185
  description: "Top winning ads for a specific advertiser id. Example: baker winning-ads winners adv_123 --top 15 --output md"
@@ -28059,7 +28229,7 @@ var winnersCommand = defineCommand169({
28059
28229
  });
28060
28230
 
28061
28231
  // src/commands/winning-ads/index.ts
28062
- var winningAdsCommand = defineCommand170({
28232
+ var winningAdsCommand = defineCommand171({
28063
28233
  meta: {
28064
28234
  name: "winning-ads",
28065
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.
@@ -28071,6 +28241,7 @@ Subcommands:
28071
28241
  baker winning-ads advertisers "<brand>" \u2014 list corpus brands \u2192 advertiser ids (for --exclude-advertiser / --advertiser-id)
28072
28242
  baker winning-ads follow "<domain|url|brand>" --platform meta|linkedin \u2014 add a brand's ads to your library
28073
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
28074
28245
  baker winning-ads winners <advertiser> \u2014 top winners for one advertiser id
28075
28246
  baker winning-ads unfollow <advertiser> \u2014 stop following a brand
28076
28247
  baker winning-ads brief \u2014 creative brief grounded in similar winners
@@ -28083,6 +28254,8 @@ Examples:
28083
28254
  baker winning-ads search "B2B SaaS before/after AI automation" --platform meta --format static --output md
28084
28255
  baker winning-ads advertisers "Acme" --output md
28085
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
28086
28259
  baker winning-ads winners adv_123 --top 15 --output md
28087
28260
  baker winning-ads hooks --platform meta --awareness problem_aware --industry saas --output md
28088
28261
  baker winning-ads search "fintech onboarding" --hook-archetype callout --winner-category winner --output md
@@ -28093,6 +28266,7 @@ Examples:
28093
28266
  advertisers: advertisersCommand2,
28094
28267
  follow: followCommand,
28095
28268
  following: followingCommand,
28269
+ feed: feedCommand,
28096
28270
  winners: winnersCommand,
28097
28271
  unfollow: unfollowCommand,
28098
28272
  brief: briefCommand,
@@ -28120,7 +28294,7 @@ function getCliVersion() {
28120
28294
  }
28121
28295
 
28122
28296
  // src/cli.ts
28123
- var main = defineCommand171({
28297
+ var main = defineCommand172({
28124
28298
  meta: {
28125
28299
  name: "baker",
28126
28300
  version: getCliVersion(),