@koda-sl/baker-cli 0.139.1 → 0.140.1-dev.5c3f9a4ae

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-TAGQCU36.js";
37
+ } from "./chunk-TXNOPO6P.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 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(),
@@ -2986,7 +3013,7 @@ var createCommand = defineCommand3({
2986
3013
  const hints = [];
2987
3014
  if (looksScheduled(name, args.description ?? "")) {
2988
3015
  hints.push(
2989
- 'This reads as recurring/scheduled work. If it should run on a cadence or a future date, create a Scheduled Action instead \u2014 baker scheduled-actions create --cron "0 9 * * MON" (or --run-at). Do NOT capture "set up a scheduled action" as a Work Action.'
3016
+ 'This reads as recurring/scheduled work. If it should run on a cadence or a future date, create a Scheduled Action instead \u2014 baker scheduled-actions create --cron "0 9 * * MON" (or --run-at; guide: __tooling__/docs/tools/baker/scheduled-actions.md). Do NOT capture "set up a scheduled action" as a Work Action.'
2990
3017
  );
2991
3018
  }
2992
3019
  hints.push(`Link dependencies: baker actions link --blocker <id> --blocked ${tempId}`);
@@ -3684,7 +3711,8 @@ Examples:
3684
3711
  baker actions claim <id>
3685
3712
  baker actions create --name "Build hero" --tags landing,creative --description "..."
3686
3713
  baker actions complete <id>
3687
- baker actions discard <id> --reason "obsolete"`
3714
+ baker actions discard <id> --reason "obsolete"
3715
+ Full guide: __tooling__/docs/tools/baker/actions.md`
3688
3716
  },
3689
3717
  subCommands: {
3690
3718
  list: listCommand2,
@@ -7657,7 +7685,8 @@ Staged writes (never touch Google until the chat is published):
7657
7685
  baker ads google budgets create --customer-id 1234567890 --name "Search" --amount 50
7658
7686
  baker ads google campaigns create --customer-id 1234567890 --name "Brand" --channel-type SEARCH --budget-ref g_temp_\u2026 --bidding-strategy MANUAL_CPC
7659
7687
  baker ads google keyword-lists create --customer-id 1234567890 --name "Brand exclusions"
7660
- baker ads google draft list \u2014 review staged Google Ads changes`
7688
+ baker ads google draft list \u2014 review staged Google Ads changes
7689
+ Full guide: __tooling__/docs/tools/baker/ads-google.md`
7661
7690
  },
7662
7691
  subCommands: {
7663
7692
  accounts: accountsCommand,
@@ -11117,7 +11146,8 @@ published; if the company's LinkedIn-write flag is off the publish is simulated)
11117
11146
 
11118
11147
  Account ID format:
11119
11148
  --account-id accepts numeric (503001492) or URN (urn:li:sponsoredAccount:503001492).
11120
- Set BAKER_LINKEDIN_AD_ACCOUNT_ID env to skip --account-id on every call.`
11149
+ Set BAKER_LINKEDIN_AD_ACCOUNT_ID env to skip --account-id on every call.
11150
+ Full guide: __tooling__/docs/tools/baker/ads-linkedin.md`
11121
11151
  },
11122
11152
  subCommands: {
11123
11153
  accounts: accountsCommand2,
@@ -11318,11 +11348,13 @@ var SPECIAL_AD_CATEGORIES = [
11318
11348
  "NONE",
11319
11349
  "HOUSING",
11320
11350
  "EMPLOYMENT",
11351
+ /** Deprecated by Meta on 2025-01-14 — replaced by FINANCIAL_PRODUCTS_SERVICES. Still accepted as input and mapped on the wire. */
11321
11352
  "CREDIT",
11322
11353
  "ISSUES_ELECTIONS_POLITICS",
11323
11354
  "FINANCIAL_PRODUCTS_SERVICES",
11324
11355
  "ONLINE_GAMBLING_AND_GAMING"
11325
11356
  ];
11357
+ var SPECIAL_CATEGORIES_REQUIRING_COUNTRY = ["ISSUES_ELECTIONS_POLITICS"];
11326
11358
  var BUYING_TYPES = ["AUCTION", "RESERVED"];
11327
11359
  var BID_STRATEGIES = [
11328
11360
  "LOWEST_COST_WITHOUT_CAP",
@@ -11331,6 +11363,10 @@ var BID_STRATEGIES = [
11331
11363
  "LOWEST_COST_WITH_MIN_ROAS"
11332
11364
  ];
11333
11365
  var BID_STRATEGIES_REQUIRING_AMOUNT = ["LOWEST_COST_WITH_BID_CAP", "COST_CAP"];
11366
+ var BID_STRATEGIES_FORBIDDING_AMOUNT = [
11367
+ "LOWEST_COST_WITHOUT_CAP",
11368
+ "LOWEST_COST_WITH_MIN_ROAS"
11369
+ ];
11334
11370
  var BILLING_EVENTS = [
11335
11371
  "APP_INSTALLS",
11336
11372
  "CLICKS",
@@ -11370,6 +11406,9 @@ var OPTIMIZATION_GOALS_REQUIRING_PAGE = [
11370
11406
  "QUALITY_LEAD",
11371
11407
  "PAGE_LIKES"
11372
11408
  ];
11409
+ var OPTIMIZATION_GOALS_REQUIRING_PIXEL = ["OFFSITE_CONVERSIONS"];
11410
+ var OPTIMIZATION_GOALS_REQUIRING_CONVERSION_SOURCE = ["VALUE"];
11411
+ var OPTIMIZATION_GOALS_REQUIRING_APP = ["APP_INSTALLS"];
11373
11412
  var DESTINATION_TYPES = [
11374
11413
  "WEBSITE",
11375
11414
  "APP",
@@ -11399,6 +11438,8 @@ var CUSTOM_EVENT_TYPES = [
11399
11438
  "OTHER"
11400
11439
  ];
11401
11440
  var ATTRIBUTION_EVENT_TYPES = ["CLICK_THROUGH", "VIEW_THROUGH", "ENGAGED_VIDEO_VIEW"];
11441
+ var ADVANTAGE_AUDIENCE_OPEN_AGE_MAX = 65;
11442
+ var ADVANTAGE_AUDIENCE_MAX_HARD_AGE_MIN = 25;
11402
11443
  var CTA_TYPES2 = [
11403
11444
  "LEARN_MORE",
11404
11445
  "SHOP_NOW",
@@ -11496,6 +11537,44 @@ var metaTargetingSchema = z14.object({
11496
11537
  device_platforms: z14.array(z14.string()).optional(),
11497
11538
  targeting_automation: z14.object({ advantage_audience: z14.union([z14.literal(0), z14.literal(1)]) }).partial().optional()
11498
11539
  }).catchall(z14.unknown());
11540
+ var GEO_INCLUSION_KEYS = [
11541
+ "countries",
11542
+ "country_groups",
11543
+ "regions",
11544
+ "cities",
11545
+ "zips",
11546
+ "geo_markets",
11547
+ "places",
11548
+ "custom_locations",
11549
+ "electoral_districts",
11550
+ "subcities",
11551
+ "neighborhoods"
11552
+ ];
11553
+ function targetingHasGeo(t) {
11554
+ const geo = t?.geo_locations;
11555
+ return Boolean(
11556
+ geo && GEO_INCLUSION_KEYS.some((key) => Array.isArray(geo[key]) && geo[key].length > 0)
11557
+ );
11558
+ }
11559
+ function targetingHasGeoOrAudience(t) {
11560
+ if (!t) {
11561
+ return false;
11562
+ }
11563
+ return targetingHasGeo(t) || Boolean(t.custom_audiences?.length);
11564
+ }
11565
+ function advantageAudienceSetting(t) {
11566
+ const value = t.targeting_automation?.advantage_audience;
11567
+ return value === 0 || value === 1 ? value : void 0;
11568
+ }
11569
+ function targetingHardLimitsDemographics(t) {
11570
+ if (!t) {
11571
+ return false;
11572
+ }
11573
+ const narrowsAgeMax = typeof t.age_max === "number" && t.age_max < ADVANTAGE_AUDIENCE_OPEN_AGE_MAX;
11574
+ const narrowsAgeMin = typeof t.age_min === "number" && t.age_min > ADVANTAGE_AUDIENCE_MAX_HARD_AGE_MIN;
11575
+ const narrowsGender = Array.isArray(t.genders) && t.genders.length === 1;
11576
+ return narrowsAgeMax || narrowsAgeMin || narrowsGender;
11577
+ }
11499
11578
  var specialAdCategoriesSchema = z14.array(z14.enum(SPECIAL_AD_CATEGORIES)).default(["NONE"]);
11500
11579
  var campaignCreateSchema3 = z14.object({
11501
11580
  name: z14.string().min(1).max(META_LIMITS.campaign.nameMax),
@@ -11519,6 +11598,14 @@ var campaignCreateSchema3 = z14.object({
11519
11598
  if (p.stop_time && p.start_time && p.stop_time <= p.start_time) {
11520
11599
  ctx.addIssue({ code: "custom", path: ["stop_time"], message: "stop_time must be after start_time" });
11521
11600
  }
11601
+ const needsCountry = p.special_ad_categories.some((c) => SPECIAL_CATEGORIES_REQUIRING_COUNTRY.includes(c));
11602
+ if (needsCountry && !p.special_ad_category_country?.length) {
11603
+ ctx.addIssue({
11604
+ code: "custom",
11605
+ path: ["special_ad_category_country"],
11606
+ message: "ISSUES_ELECTIONS_POLITICS campaigns need special_ad_category_country \u2014 pass the 2-letter country code(s) the ads run in"
11607
+ });
11608
+ }
11522
11609
  });
11523
11610
  var campaignUpdateSchema3 = z14.object({
11524
11611
  name: z14.string().min(1).max(META_LIMITS.campaign.nameMax).optional(),
@@ -11570,8 +11657,41 @@ var adSetFields = {
11570
11657
  attribution_spec: attributionSpecSchema.optional(),
11571
11658
  start_time: z14.number().int().positive().optional(),
11572
11659
  end_time: z14.number().int().positive().optional(),
11573
- targeting: metaTargetingSchema
11660
+ targeting: metaTargetingSchema,
11661
+ /** EU Digital Services Act: who benefits from / pays for the ad. Auto-filled from the account for EU geo when omitted. */
11662
+ dsa_beneficiary: z14.string().min(1).max(100).optional(),
11663
+ dsa_payor: z14.string().min(1).max(100).optional()
11574
11664
  };
11665
+ function validateBidStrategy(p, ctx) {
11666
+ const strategy = p.bid_strategy;
11667
+ if (!strategy) {
11668
+ return;
11669
+ }
11670
+ if (BID_STRATEGIES_REQUIRING_AMOUNT.includes(strategy) && !p.bidAmount) {
11671
+ ctx.addIssue({ code: "custom", path: ["bidAmount"], message: `${strategy} requires a bidAmount` });
11672
+ }
11673
+ if (BID_STRATEGIES_FORBIDDING_AMOUNT.includes(strategy) && p.bidAmount) {
11674
+ ctx.addIssue({
11675
+ code: "custom",
11676
+ path: ["bidAmount"],
11677
+ message: `${strategy} must not set a bidAmount \u2014 Meta rejects one (it bids automatically)`
11678
+ });
11679
+ }
11680
+ if (strategy === "COST_CAP" && p.billing_event && p.billing_event !== "IMPRESSIONS") {
11681
+ ctx.addIssue({
11682
+ code: "custom",
11683
+ path: ["billing_event"],
11684
+ message: "COST_CAP requires billing_event IMPRESSIONS"
11685
+ });
11686
+ }
11687
+ if (strategy === "LOWEST_COST_WITH_MIN_ROAS" && p.optimization_goal && p.optimization_goal !== "VALUE") {
11688
+ ctx.addIssue({
11689
+ code: "custom",
11690
+ path: ["bid_strategy"],
11691
+ message: "LOWEST_COST_WITH_MIN_ROAS requires optimization_goal VALUE"
11692
+ });
11693
+ }
11694
+ }
11575
11695
  function validateAdSetBudgetAndBid(p, ctx) {
11576
11696
  if (p.dailyBudget && p.lifetimeBudget) {
11577
11697
  ctx.addIssue({ code: "custom", path: ["dailyBudget"], message: "set only one of dailyBudget or lifetimeBudget" });
@@ -11580,25 +11700,74 @@ function validateAdSetBudgetAndBid(p, ctx) {
11580
11700
  if (p.lifetimeBudget && !p.end_time) {
11581
11701
  ctx.addIssue({ code: "custom", path: ["end_time"], message: "lifetimeBudget ad sets need an end_time" });
11582
11702
  }
11583
- if (p.bid_strategy && BID_STRATEGIES_REQUIRING_AMOUNT.includes(p.bid_strategy) && !p.bidAmount) {
11584
- ctx.addIssue({ code: "custom", path: ["bidAmount"], message: `${p.bid_strategy} requires a bidAmount` });
11585
- }
11703
+ validateBidStrategy(p, ctx);
11586
11704
  if (p.end_time && p.start_time && p.end_time <= p.start_time) {
11587
11705
  ctx.addIssue({ code: "custom", path: ["end_time"], message: "end_time must be after start_time" });
11588
11706
  }
11589
11707
  }
11590
11708
  function validateAdSetPromotedObject(p, ctx) {
11591
- if (p.optimization_goal && OPTIMIZATION_GOALS_REQUIRING_PAGE.includes(p.optimization_goal) && !p.promoted_object?.page_id) {
11709
+ const goal = p.optimization_goal;
11710
+ if (!goal) {
11711
+ return;
11712
+ }
11713
+ const po = p.promoted_object;
11714
+ if (OPTIMIZATION_GOALS_REQUIRING_PAGE.includes(goal) && !po?.page_id) {
11592
11715
  ctx.addIssue({
11593
11716
  code: "custom",
11594
11717
  path: ["promoted_object", "page_id"],
11595
- message: `optimization_goal ${p.optimization_goal} needs promoted_object.page_id \u2014 pass --page-id with the Facebook Page the leads/likes belong to`
11718
+ message: `optimization_goal ${goal} needs promoted_object.page_id \u2014 pass --page-id with the Facebook Page the leads/likes belong to`
11719
+ });
11720
+ }
11721
+ if (OPTIMIZATION_GOALS_REQUIRING_PIXEL.includes(goal) && !po?.pixel_id) {
11722
+ ctx.addIssue({
11723
+ code: "custom",
11724
+ path: ["promoted_object", "pixel_id"],
11725
+ message: `optimization_goal ${goal} needs promoted_object.pixel_id \u2014 pass --pixel-id with the conversion pixel`
11726
+ });
11727
+ }
11728
+ if (OPTIMIZATION_GOALS_REQUIRING_CONVERSION_SOURCE.includes(goal) && !po?.pixel_id && !po?.application_id) {
11729
+ ctx.addIssue({
11730
+ code: "custom",
11731
+ path: ["promoted_object", "pixel_id"],
11732
+ message: `optimization_goal ${goal} needs a conversion source \u2014 pass promoted_object.pixel_id (web value optimization) or promoted_object.application_id (app value optimization)`
11733
+ });
11734
+ }
11735
+ if ((OPTIMIZATION_GOALS_REQUIRING_PIXEL.includes(goal) || OPTIMIZATION_GOALS_REQUIRING_CONVERSION_SOURCE.includes(goal)) && !po?.custom_event_type) {
11736
+ ctx.addIssue({
11737
+ code: "custom",
11738
+ path: ["promoted_object", "custom_event_type"],
11739
+ message: `optimization_goal ${goal} needs promoted_object.custom_event_type \u2014 pass --custom-event-type (e.g. PURCHASE, LEAD)`
11740
+ });
11741
+ }
11742
+ if (OPTIMIZATION_GOALS_REQUIRING_APP.includes(goal) && !po?.application_id) {
11743
+ ctx.addIssue({
11744
+ code: "custom",
11745
+ path: ["promoted_object", "application_id"],
11746
+ message: `optimization_goal ${goal} needs promoted_object.application_id`
11747
+ });
11748
+ }
11749
+ }
11750
+ function validateAdvantageAudience(p, ctx) {
11751
+ const t = p.targeting;
11752
+ if (t && targetingHardLimitsDemographics(t) && advantageAudienceSetting(t) === 1) {
11753
+ ctx.addIssue({
11754
+ code: "custom",
11755
+ path: ["targeting", "targeting_automation", "advantage_audience"],
11756
+ message: "Advantage+ Audience (advantage_audience: 1) treats age and gender as suggestions, so a hard age/gender limit is rejected \u2014 set advantage_audience: 0 to enforce the limit, or drop the age_max/age_min/gender restriction"
11596
11757
  });
11597
11758
  }
11598
11759
  }
11599
11760
  var adSetCreateSchema = z14.object(adSetFields).superRefine((p, ctx) => {
11600
11761
  validateAdSetBudgetAndBid(p, ctx);
11601
11762
  validateAdSetPromotedObject(p, ctx);
11763
+ validateAdvantageAudience(p, ctx);
11764
+ if (!targetingHasGeoOrAudience(p.targeting)) {
11765
+ ctx.addIssue({
11766
+ code: "custom",
11767
+ path: ["targeting", "geo_locations"],
11768
+ message: 'targeting needs a location \u2014 set targeting.geo_locations (e.g. countries: ["US"]) or include a custom audience; Meta rejects an ad set with no geo'
11769
+ });
11770
+ }
11602
11771
  });
11603
11772
  var adSetUpdateSchema = z14.object({
11604
11773
  name: adSetFields.name.optional(),
@@ -11613,7 +11782,9 @@ var adSetUpdateSchema = z14.object({
11613
11782
  attribution_spec: attributionSpecSchema.optional(),
11614
11783
  start_time: z14.number().int().positive().optional(),
11615
11784
  end_time: z14.number().int().positive().optional(),
11616
- targeting: metaTargetingSchema.optional()
11785
+ targeting: metaTargetingSchema.optional(),
11786
+ dsa_beneficiary: z14.string().min(1).max(100).optional(),
11787
+ dsa_payor: z14.string().min(1).max(100).optional()
11617
11788
  }).superRefine((p, ctx) => {
11618
11789
  if (!Object.values(p).some((val) => val !== void 0)) {
11619
11790
  ctx.addIssue({ code: "custom", message: "update needs at least one field" });
@@ -11622,6 +11793,7 @@ var adSetUpdateSchema = z14.object({
11622
11793
  if (p.optimization_goal) {
11623
11794
  validateAdSetPromotedObject(p, ctx);
11624
11795
  }
11796
+ validateAdvantageAudience(p, ctx);
11625
11797
  });
11626
11798
  var messageSchema = z14.string().min(1).max(META_LIMITS.creative.messageHardMax);
11627
11799
  var headlineSchema2 = z14.string().min(1).max(META_LIMITS.creative.headlineMax);
@@ -13400,7 +13572,8 @@ Performance (the workhorse):
13400
13572
 
13401
13573
  Audit & review:
13402
13574
  baker ads meta activities --account-id act_123 --days 14
13403
- baker ads meta preview --creative-id 9988 --ad-format MOBILE_FEED_STANDARD --out-file /tmp/p.html`
13575
+ baker ads meta preview --creative-id 9988 --ad-format MOBILE_FEED_STANDARD --out-file /tmp/p.html
13576
+ Full guide: __tooling__/docs/tools/baker/ads-meta.md`
13404
13577
  },
13405
13578
  subCommands: {
13406
13579
  accounts: accountsCommand3,
@@ -14864,7 +15037,8 @@ Examples:
14864
15037
  --start-time 2026-04-01T00:00:00Z --end-time 2026-05-01T00:00:00Z \\
14865
15038
  --metric-groups ENGAGEMENT,BILLING --segmentation-type LOCATIONS
14866
15039
 
14867
- The CLI auto-detects --account-id when exactly one X Ads account is connected, or via BAKER_X_ADS_ACCOUNT_ID.`
15040
+ The CLI auto-detects --account-id when exactly one X Ads account is connected, or via BAKER_X_ADS_ACCOUNT_ID.
15041
+ Full guide: __tooling__/docs/tools/baker/ads-x.md`
14868
15042
  },
14869
15043
  subCommands: {
14870
15044
  accounts: accountsCommand4,
@@ -14902,7 +15076,8 @@ Examples:
14902
15076
  baker ads meta insights --object act_123 --level ad --date-preset last_7d --fields impressions,spend,ctr
14903
15077
  baker ads linkedin accounts
14904
15078
  baker ads x accounts
14905
- baker ads x stats sync --preset campaign-engagement-7d --entity-ids abc,def`
15079
+ baker ads x stats sync --preset campaign-engagement-7d --entity-ids abc,def
15080
+ Full guides: __tooling__/docs/tools/baker/ads-<platform>.md (google|meta|linkedin|x)`
14906
15081
  },
14907
15082
  subCommands: {
14908
15083
  google: googleCommand,
@@ -21599,7 +21774,8 @@ Subcommands:
21599
21774
  baker canvas catalog \u2014 print the agent-facing node + composition catalog (JSON Schema)
21600
21775
  baker canvas inspect <run_id> \u2014 one-page summary of a completed run
21601
21776
  baker canvas scaffold-video <video> \u2014 turn a reference video into a runnable reproduction canvas (deconstruct + recurring-element detection)
21602
- baker canvas scaffold-static-ad <image> \u2014 turn a source image into a runnable static-ad canvas (describe + element detection)`
21777
+ baker canvas scaffold-static-ad <image> \u2014 turn a source image into a runnable static-ad canvas (describe + element detection)
21778
+ Full guide: __tooling__/docs/tools/baker/canvas.md`
21603
21779
  },
21604
21780
  subCommands: {
21605
21781
  run: runCommand,
@@ -21836,7 +22012,8 @@ var diffCommand = defineCommand96({
21836
22012
  var chatsCommand = defineCommand96({
21837
22013
  meta: {
21838
22014
  name: "chats",
21839
- description: "Inspect other Sessions on this account (read-only): list them, view what they produced, read their conversation, and see their real file changes \u2014 so you can reuse past work for a new goal."
22015
+ description: `Inspect other Sessions on this account (read-only): list them, view what they produced, read their conversation, and see their real file changes \u2014 so you can reuse past work for a new goal.
22016
+ Full guide: __tooling__/docs/tools/baker/chats.md`
21840
22017
  },
21841
22018
  subCommands: {
21842
22019
  list: listCommand5,
@@ -22060,7 +22237,8 @@ var creativesCommand3 = defineCommand98({
22060
22237
  Static creative handoff:
22061
22238
  baker creatives publish ./canvas/run/final.png --title "Spring Offer Static Ad"
22062
22239
 
22063
- Publishing uploads the image to the Company image library, applies the official creative tag, and returns an image reference for chat previews.`
22240
+ Publishing uploads the image to the Company image library, applies the official creative tag, and returns an image reference for chat previews.
22241
+ Full guide: __tooling__/docs/tools/baker/creatives.md`
22064
22242
  },
22065
22243
  subCommands: {
22066
22244
  publish: publishCommand
@@ -22268,7 +22446,8 @@ This command is READ-ONLY and never reveals secret values. Author a Form's struc
22268
22446
  Examples:
22269
22447
  baker flows list # every Form + how many confidential fields still need setup
22270
22448
  baker flows show contact # one Form's confidential fields and their status
22271
- baker flows show contact --full`
22449
+ baker flows show contact --full
22450
+ Full guide: __tooling__/docs/tools/baker/flows.md`
22272
22451
  },
22273
22452
  subCommands: {
22274
22453
  list: listCommand6,
@@ -22677,7 +22856,8 @@ Examples:
22677
22856
  baker ga4 audit \u2014 full property health check
22678
22857
  baker ga4 query --preset tracking-health \u2014 compare GA4 vs GAds data
22679
22858
  baker ga4 query --preset lp-performance --days 14 \u2014 landing page UX audit
22680
- baker ga4 query --dimensions "date" --metrics "sessions" --days 7 \u2014 free-form query`
22859
+ baker ga4 query --dimensions "date" --metrics "sessions" --days 7 \u2014 free-form query
22860
+ Full guide: __tooling__/docs/tools/baker/ga4.md`
22681
22861
  },
22682
22862
  subCommands: {
22683
22863
  properties: propertiesCommand,
@@ -23086,7 +23266,8 @@ Examples:
23086
23266
  baker gsc query --preset cannibalization \u2014 find PPC-SEO overlap
23087
23267
  baker gsc query --preset brand-halo --brand "Acme" --days 90 \u2014 brand search trends
23088
23268
  baker gsc query --dimensions "query,page" --days 28 \u2014 free-form query
23089
- baker gsc sitemaps \u2014 check sitemap health`
23269
+ baker gsc sitemaps \u2014 check sitemap health
23270
+ Full guide: __tooling__/docs/tools/baker/gsc.md`
23090
23271
  },
23091
23272
  subCommands: {
23092
23273
  sites: sitesCommand,
@@ -23200,7 +23381,8 @@ var listCommand7 = defineCommand108({
23200
23381
  var historyCommand = defineCommand108({
23201
23382
  meta: {
23202
23383
  name: "history",
23203
- description: "Unified account history (audit log): what changed, who did it, and when."
23384
+ description: `Unified account history (audit log): what changed, who did it, and when.
23385
+ Full guide: __tooling__/docs/tools/baker/history.md`
23204
23386
  },
23205
23387
  subCommands: { list: listCommand7 }
23206
23388
  });
@@ -25798,7 +25980,8 @@ Taxonomy:
25798
25980
  baker images tags List available image tag names (defaults + company custom tags)
25799
25981
 
25800
25982
  Paid transforms (run on the Convex backend, cost-tracked):
25801
- baker images upscale <imageId> Real-ESRGAN super-resolution ($0.05/image, waits for completion)`
25983
+ baker images upscale <imageId> Real-ESRGAN super-resolution ($0.05/image, waits for completion)
25984
+ Full guide: __tooling__/docs/tools/baker/images.md`
25802
25985
  },
25803
25986
  subCommands: {
25804
25987
  get: getCommand2,
@@ -25862,6 +26045,53 @@ function fail4(err) {
25862
26045
  writeJson({ ok: false, error: { code: "NETWORK_ERROR", message: "Unexpected error" } });
25863
26046
  process.exit(1);
25864
26047
  }
26048
+ registerSchema({
26049
+ command: "mcp.connected",
26050
+ description: "List every third-party tool this chat can reach: managed integrations (Attio, Slack, Gmail, Google Sheets, \u2026) plus custom MCP servers. Start here when the user mentions an external tool.",
26051
+ args: {}
26052
+ });
26053
+ var connectedCommand = defineCommand133({
26054
+ meta: {
26055
+ name: "connected",
26056
+ description: `List all connected third-party tools \u2014 managed integrations plus custom MCP servers.
26057
+
26058
+ Managed tools ride the composio servers (mcp__composio_<scope>__*); custom servers expose mcp__<name>__*.
26059
+ A tool the user names that is NOT listed here is simply not connected yet.`
26060
+ },
26061
+ run: async () => {
26062
+ try {
26063
+ const user = actingUserId();
26064
+ const data = await apiGet("/api/mcp/connected", user ? { actingUserId: user } : void 0);
26065
+ const managed = data.managed.map((c) => ({
26066
+ toolkit: c.toolkitSlug,
26067
+ scope: c.scope,
26068
+ ...c.status !== "ACTIVE" ? { status: c.status } : {},
26069
+ ...c.accountLabel ? { account: c.accountLabel } : {},
26070
+ ...c.readOnly ? { readOnly: true } : {}
26071
+ }));
26072
+ const custom = data.custom.map((s) => ({ name: s.name, scope: s.scope, enabled: s.enabled }));
26073
+ const hints = [];
26074
+ if (data.managedDisabled) {
26075
+ hints.push("Managed integrations are disabled for this company \u2014 only custom MCP servers load.");
26076
+ }
26077
+ if ([...data.managed, ...data.custom].some((e) => e.scope === "user" || e.scope === "user_org")) {
26078
+ hints.push(
26079
+ "user/user_org entries are personal: they belong to the current message sender and load only in their turns \u2014 other users in this chat won't have them."
26080
+ );
26081
+ }
26082
+ hints.push(
26083
+ "Not listed = not connected. Managed app (Slack, HubSpot, Attio, \u2026) \u2192 the user connects it in the dashboard: Brain \u2192 Integrations \u2192 Tools. Custom HTTPS MCP endpoint \u2192 `baker mcp add`."
26084
+ );
26085
+ writeJson({
26086
+ ok: true,
26087
+ data: { ...data.managedDisabled ? { managedDisabled: true } : {}, managed, custom },
26088
+ hints
26089
+ });
26090
+ } catch (err) {
26091
+ fail4(err);
26092
+ }
26093
+ }
26094
+ });
25865
26095
  registerSchema({
25866
26096
  command: "mcp.list",
25867
26097
  description: "List the custom MCP servers this company's chats see (org + company + your own user scope).",
@@ -25958,20 +26188,22 @@ Example:
25958
26188
  var mcpCommand = defineCommand133({
25959
26189
  meta: {
25960
26190
  name: "mcp",
25961
- description: `Custom MCP servers for this company \u2014 point the agent at any HTTPS MCP endpoint.
26191
+ description: `Third-party tools for this company \u2014 see what's connected, register custom HTTPS MCP endpoints.
25962
26192
 
25963
26193
  Tools from a registered server appear as mcp__<name>__* on the next message.
25964
26194
  Managed here are **company-scoped** servers; user- and org-scoped servers are managed in the dashboard.
25965
26195
 
25966
26196
  Start here:
25967
- baker mcp list
26197
+ baker mcp connected # everything this chat can reach: managed integrations + custom servers
25968
26198
 
25969
26199
  Examples:
25970
26200
  baker mcp add --name weather --url https://mcp.example.com/mcp
25971
26201
  baker mcp add --name acme --url https://acme.dev/mcp --header "Authorization: Bearer sk-\u2026"
25972
- baker mcp remove --name weather`
26202
+ baker mcp remove --name weather
26203
+ Full guide: __tooling__/docs/tools/baker/mcp.md`
25973
26204
  },
25974
26205
  subCommands: {
26206
+ connected: connectedCommand,
25975
26207
  list: listCommand8,
25976
26208
  add: addCommand,
25977
26209
  remove: removeCommand4
@@ -26843,7 +27075,8 @@ Examples:
26843
27075
  baker research advertisers "running shoes"
26844
27076
  baker research intent "buy running shoes,best running shoes 2026"
26845
27077
  baker research keywords-for-site "competitor.com"
26846
- baker research keyword-gap "them.com" "us.com"`
27078
+ baker research keyword-gap "them.com" "us.com"
27079
+ Full guide: __tooling__/docs/tools/baker/research.md`
26847
27080
  },
26848
27081
  subCommands: {
26849
27082
  web: webCommand,
@@ -27290,7 +27523,8 @@ Examples:
27290
27523
  baker scheduled-actions create --name "Weekly report" --description "Prepare weekly report" --cron "0 9 * * MON"
27291
27524
  baker scheduled-actions update <id-or-temp_sched_id> --enabled false
27292
27525
  baker scheduled-actions delete <id-or-temp_sched_id>
27293
- baker scheduled-actions trigger <id>`
27526
+ baker scheduled-actions trigger <id>
27527
+ Full guide: __tooling__/docs/tools/baker/scheduled-actions.md`
27294
27528
  },
27295
27529
  subCommands: {
27296
27530
  list: listCommand9,
@@ -27326,11 +27560,17 @@ var schemaCommand = defineCommand152({
27326
27560
  const schema = getSchema(commandName);
27327
27561
  if (!schema) {
27328
27562
  const available = listSchemas();
27563
+ const segments = commandName.split(".");
27564
+ const family = segments[0] === "ads" && segments[1] ? `ads-${segments[1]}` : segments[0];
27329
27565
  writeJson({
27330
27566
  ok: false,
27331
27567
  error: {
27332
27568
  code: "NOT_FOUND",
27333
- message: `Schema "${commandName}" not found. Available: ${available.join(", ")}`
27569
+ message: `Schema "${commandName}" not found. Available: ${available.join(", ")}`,
27570
+ fix: {
27571
+ action: `Run the command with --help for exact args, and read __tooling__/docs/tools/baker/${family}.md`,
27572
+ explanation: "Not every command registers a schema; --help and the family doc are the ground truth for those."
27573
+ }
27334
27574
  }
27335
27575
  });
27336
27576
  process.exit(1);
@@ -27447,7 +27687,8 @@ Refs: \`baker tags list\` prints each tag's ref \u2014 a real tag id, or tag_tem
27447
27687
 
27448
27688
  Examples:
27449
27689
  baker tags list # production + staged, full readable config + secret status
27450
- baker tags draft # review the staged changes awaiting publish`
27690
+ baker tags draft # review the staged changes awaiting publish
27691
+ Full guide: __tooling__/docs/tools/baker/tags.md`
27451
27692
  },
27452
27693
  subCommands: {
27453
27694
  list: listCommand10,
@@ -27730,7 +27971,8 @@ Examples:
27730
27971
  baker testimonials search "value for money" --rating-min 4
27731
27972
  baker testimonials get <testimonial-id>
27732
27973
  baker testimonials list --source google --sentiment positive
27733
- baker testimonials tags`
27974
+ baker testimonials tags
27975
+ Full guide: __tooling__/docs/tools/baker/testimonials.md`
27734
27976
  },
27735
27977
  subCommands: {
27736
27978
  get: getCommand4,
@@ -28005,7 +28247,8 @@ Examples:
28005
28247
  baker videos get <video-id>
28006
28248
  baker videos upload ./demo.mp4
28007
28249
  baker videos delete <video-id> --dry-run
28008
- baker videos tags`
28250
+ baker videos tags
28251
+ Full guide: __tooling__/docs/tools/baker/videos.md`
28009
28252
  },
28010
28253
  subCommands: {
28011
28254
  get: getCommand5,
@@ -28017,7 +28260,7 @@ Examples:
28017
28260
  });
28018
28261
 
28019
28262
  // src/commands/winning-ads/index.ts
28020
- import { defineCommand as defineCommand173 } from "citty";
28263
+ import { defineCommand as defineCommand174 } from "citty";
28021
28264
 
28022
28265
  // src/commands/winning-ads/advertisers.ts
28023
28266
  import { defineCommand as defineCommand163 } from "citty";
@@ -28411,8 +28654,116 @@ var followCommand = defineCommand166({
28411
28654
  }
28412
28655
  });
28413
28656
 
28414
- // src/commands/winning-ads/following.ts
28657
+ // src/commands/winning-ads/follow-competitors.ts
28415
28658
  import { defineCommand as defineCommand167 } from "citty";
28659
+ var PLATFORMS2 = ["meta", "linkedin"];
28660
+ var BATCH_TIMEOUT_MS = 3e5;
28661
+ function buildFollowBatchBody(input) {
28662
+ const seen = /* @__PURE__ */ new Set();
28663
+ const inputs = [];
28664
+ for (const domain of splitList(input.domains)) {
28665
+ const key = domain.toLowerCase();
28666
+ if (seen.has(key)) {
28667
+ continue;
28668
+ }
28669
+ seen.add(key);
28670
+ inputs.push(domain);
28671
+ }
28672
+ return { inputs, platform: input.platform ?? "meta" };
28673
+ }
28674
+ registerSchema({
28675
+ command: "winning-ads.follow-competitors",
28676
+ 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.",
28677
+ args: {
28678
+ domains: {
28679
+ type: "string",
28680
+ 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"`,
28681
+ required: true
28682
+ },
28683
+ platform: {
28684
+ type: "string",
28685
+ description: "meta|linkedin \u2014 how to read each input, not a tracking limit (default meta). Domains resolve both.",
28686
+ required: false,
28687
+ default: "meta"
28688
+ }
28689
+ }
28690
+ });
28691
+ var followCompetitorsCommand = defineCommand167({
28692
+ meta: {
28693
+ name: "follow-competitors",
28694
+ 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"'
28695
+ },
28696
+ args: {
28697
+ domains: {
28698
+ type: "positional",
28699
+ description: "Comma-separated domains (best), URLs, or brand names",
28700
+ required: true
28701
+ },
28702
+ platform: {
28703
+ type: "string",
28704
+ description: "meta|linkedin \u2014 how to read each input, not a tracking limit (default meta).",
28705
+ required: false,
28706
+ default: "meta"
28707
+ }
28708
+ },
28709
+ run: async ({ args }) => {
28710
+ try {
28711
+ const platform = String(args.platform ?? "meta");
28712
+ if (!PLATFORMS2.includes(platform)) {
28713
+ writeJson({
28714
+ ok: false,
28715
+ error: { code: "VALIDATION_ERROR", message: "--platform must be one of: meta, linkedin" }
28716
+ });
28717
+ process.exit(1);
28718
+ }
28719
+ const body = buildFollowBatchBody({ domains: String(args.domains ?? ""), platform });
28720
+ if (body.inputs.length === 0) {
28721
+ writeJson({
28722
+ ok: false,
28723
+ error: { code: "VALIDATION_ERROR", message: "Pass at least one domain in --domains." }
28724
+ });
28725
+ process.exit(1);
28726
+ }
28727
+ if (body.inputs.length > FOLLOW_BATCH_MAX) {
28728
+ writeJson({
28729
+ ok: false,
28730
+ error: {
28731
+ code: "VALIDATION_ERROR",
28732
+ message: `Too many brands (${body.inputs.length}). Follow at most ${FOLLOW_BATCH_MAX} per call.`
28733
+ }
28734
+ });
28735
+ process.exit(1);
28736
+ }
28737
+ const data = await apiPost("/api/ad-library/follow-batch", body, {
28738
+ timeoutMs: BATCH_TIMEOUT_MS
28739
+ });
28740
+ const hints = [];
28741
+ if (data.failed_count > 0) {
28742
+ const failed = data.results.filter((r) => r.status === "failed").map((r) => r.input);
28743
+ hints.push(
28744
+ `${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\`.`
28745
+ );
28746
+ }
28747
+ if (data.ambiguous_count > 0) {
28748
+ const ambiguous = data.results.filter((r) => r.status === "ambiguous").map((r) => r.input);
28749
+ hints.push(
28750
+ `${data.ambiguous_count} brand(s) matched multiple advertisers: ${ambiguous.join(", ")}. Re-run \`baker winning-ads follow\` with an exact domain/URL to pick one.`
28751
+ );
28752
+ }
28753
+ if (data.added_count > 0) {
28754
+ hints.push(
28755
+ "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`."
28756
+ );
28757
+ }
28758
+ writeJson({ ok: true, data, hints: hints.length > 0 ? hints : void 0 });
28759
+ } catch (err) {
28760
+ reportError(err);
28761
+ }
28762
+ }
28763
+ });
28764
+
28765
+ // src/commands/winning-ads/following.ts
28766
+ import { defineCommand as defineCommand168 } from "citty";
28416
28767
  registerSchema({
28417
28768
  command: "winning-ads.following",
28418
28769
  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 +28796,7 @@ function followingNormalizer(record, full) {
28445
28796
  platforms: Array.isArray(record.platforms) ? record.platforms : []
28446
28797
  };
28447
28798
  }
28448
- var followingCommand = defineCommand167({
28799
+ var followingCommand = defineCommand168({
28449
28800
  meta: {
28450
28801
  name: "following",
28451
28802
  description: "List brands you follow, with status (ready / adding\u2026) and cached counts. Example: baker winning-ads following --output md"
@@ -28480,7 +28831,7 @@ var followingCommand = defineCommand167({
28480
28831
  });
28481
28832
 
28482
28833
  // src/commands/winning-ads/patterns.ts
28483
- import { defineCommand as defineCommand168 } from "citty";
28834
+ import { defineCommand as defineCommand169 } from "citty";
28484
28835
  registerSchema({
28485
28836
  command: "winning-ads.patterns",
28486
28837
  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 +28870,7 @@ function discriminatorRow(record) {
28519
28870
  top_values_duds: Array.isArray(record.top_values_b) ? record.top_values_b.join(", ") : ""
28520
28871
  };
28521
28872
  }
28522
- var patternsCommand = defineCommand168({
28873
+ var patternsCommand = defineCommand169({
28523
28874
  meta: {
28524
28875
  name: "patterns",
28525
28876
  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 +28926,7 @@ var patternsCommand = defineCommand168({
28575
28926
  });
28576
28927
 
28577
28928
  // src/commands/winning-ads/search.ts
28578
- import { defineCommand as defineCommand169 } from "citty";
28929
+ import { defineCommand as defineCommand170 } from "citty";
28579
28930
  registerSchema({
28580
28931
  command: "winning-ads.search",
28581
28932
  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 +29034,7 @@ function buildSearchBody(args) {
28683
29034
  }
28684
29035
  return body;
28685
29036
  }
28686
- var searchCommand4 = defineCommand169({
29037
+ var searchCommand4 = defineCommand170({
28687
29038
  meta: {
28688
29039
  name: "search",
28689
29040
  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 +29149,7 @@ var searchCommand4 = defineCommand169({
28798
29149
  });
28799
29150
 
28800
29151
  // src/commands/winning-ads/seeds.ts
28801
- import { defineCommand as defineCommand170 } from "citty";
29152
+ import { defineCommand as defineCommand171 } from "citty";
28802
29153
  function leanRow(r) {
28803
29154
  return {
28804
29155
  key: r.key,
@@ -28826,7 +29177,7 @@ function makeSeedCommand(opts) {
28826
29177
  limit: { type: "number", description: "Max keys 1-100 (default 20)", required: false, default: 20 }
28827
29178
  }
28828
29179
  });
28829
- return defineCommand170({
29180
+ return defineCommand171({
28830
29181
  meta: { name: opts.name, description: opts.description },
28831
29182
  args: {
28832
29183
  platform: { type: "string", description: "Single platform to segment on", required: false },
@@ -28875,7 +29226,7 @@ var formatsCommand = makeSeedCommand({
28875
29226
  });
28876
29227
 
28877
29228
  // src/commands/winning-ads/unfollow.ts
28878
- import { defineCommand as defineCommand171 } from "citty";
29229
+ import { defineCommand as defineCommand172 } from "citty";
28879
29230
  registerSchema({
28880
29231
  command: "winning-ads.unfollow",
28881
29232
  description: "Stop following a brand \u2014 removes it from your ad-dna library by advertiser id.",
@@ -28883,7 +29234,7 @@ registerSchema({
28883
29234
  advertiser: { type: "string", description: "Advertiser id to unfollow", required: true }
28884
29235
  }
28885
29236
  });
28886
- var unfollowCommand = defineCommand171({
29237
+ var unfollowCommand = defineCommand172({
28887
29238
  meta: {
28888
29239
  name: "unfollow",
28889
29240
  description: "Stop following a brand by advertiser id. Example: baker winning-ads unfollow adv_123"
@@ -28904,7 +29255,7 @@ var unfollowCommand = defineCommand171({
28904
29255
  });
28905
29256
 
28906
29257
  // src/commands/winning-ads/winners.ts
28907
- import { defineCommand as defineCommand172 } from "citty";
29258
+ import { defineCommand as defineCommand173 } from "citty";
28908
29259
  registerSchema({
28909
29260
  command: "winning-ads.winners",
28910
29261
  description: "Top winning ads for one advertiser id (from `advertisers` or `following`). Returns lean winner cards; add --full for DNA + longevity.",
@@ -28914,7 +29265,7 @@ registerSchema({
28914
29265
  platform: { type: "string", description: "Filter to a single platform: meta|linkedin", required: false }
28915
29266
  }
28916
29267
  });
28917
- var winnersCommand = defineCommand172({
29268
+ var winnersCommand = defineCommand173({
28918
29269
  meta: {
28919
29270
  name: "winners",
28920
29271
  description: "Top winning ads for a specific advertiser id. Example: baker winning-ads winners adv_123 --top 15 --output md"
@@ -28964,7 +29315,7 @@ var winnersCommand = defineCommand172({
28964
29315
  });
28965
29316
 
28966
29317
  // src/commands/winning-ads/index.ts
28967
- var winningAdsCommand = defineCommand173({
29318
+ var winningAdsCommand = defineCommand174({
28968
29319
  meta: {
28969
29320
  name: "winning-ads",
28970
29321
  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 +29326,7 @@ Subcommands:
28975
29326
  baker winning-ads search "<free text>" \u2014 semantic search across the corpus; lean shortlist (advertiser, summary, scores, media_url)
28976
29327
  baker winning-ads advertisers "<brand>" \u2014 list corpus brands \u2192 advertiser ids (for --exclude-advertiser / --advertiser-id)
28977
29328
  baker winning-ads follow "<domain|url|brand>" --platform meta|linkedin \u2014 add a brand's ads to your library
29329
+ baker winning-ads follow-competitors "<domain,domain,...>" \u2014 add MANY brands at once (every competitor by domain)
28978
29330
  baker winning-ads following \u2014 list brands you follow (status + counts)
28979
29331
  baker winning-ads feed \u2014 winners across EVERY brand you follow; trim with --advertiser
28980
29332
  baker winning-ads winners <advertiser> \u2014 top winners for one advertiser id
@@ -28989,17 +29341,20 @@ Examples:
28989
29341
  baker winning-ads search "B2B SaaS before/after AI automation" --platform meta --format static --output md
28990
29342
  baker winning-ads advertisers "Acme" --output md
28991
29343
  baker winning-ads follow "deel.com" --platform meta
29344
+ baker winning-ads follow-competitors "deel.com,notion.so,hubspot.com"
28992
29345
  baker winning-ads feed --per-advertiser 5 --output md
28993
29346
  baker winning-ads feed --advertiser adv_123,adv_456 --platform meta --output md
28994
29347
  baker winning-ads winners adv_123 --top 15 --output md
28995
29348
  baker winning-ads hooks --platform meta --awareness problem_aware --industry saas --output md
28996
29349
  baker winning-ads search "fintech onboarding" --hook-archetype callout --winner-category winner --output md
28997
- baker winning-ads patterns --winners a_1,a_2 --duds a_9,a_8 --output md`
29350
+ baker winning-ads patterns --winners a_1,a_2 --duds a_9,a_8 --output md
29351
+ Full guide: __tooling__/docs/tools/baker/winning-ads.md`
28998
29352
  },
28999
29353
  subCommands: {
29000
29354
  search: searchCommand4,
29001
29355
  advertisers: advertisersCommand2,
29002
29356
  follow: followCommand,
29357
+ "follow-competitors": followCompetitorsCommand,
29003
29358
  following: followingCommand,
29004
29359
  feed: feedCommand,
29005
29360
  winners: winnersCommand,
@@ -29029,7 +29384,7 @@ function getCliVersion() {
29029
29384
  }
29030
29385
 
29031
29386
  // src/cli.ts
29032
- var main = defineCommand174({
29387
+ var main = defineCommand175({
29033
29388
  meta: {
29034
29389
  name: "baker",
29035
29390
  version: getCliVersion(),