@koda-sl/baker-cli 0.408.0 → 0.409.0

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
@@ -97,7 +97,7 @@ import {
97
97
  withoutCastName,
98
98
  wordCount,
99
99
  ytDlpBlockSignal
100
- } from "./chunk-KMCVSEGU.js";
100
+ } from "./chunk-QWRXQ2UC.js";
101
101
  import {
102
102
  NAME_CONTAINS_ARG,
103
103
  csvOrJson,
@@ -108,7 +108,7 @@ import {
108
108
  resolveEffectiveStatus,
109
109
  statusFilterHints,
110
110
  todayIso
111
- } from "./chunk-3ZN2DUBU.js";
111
+ } from "./chunk-ZIOW64BL.js";
112
112
  import {
113
113
  buildQueryCacheKey,
114
114
  cacheGet,
@@ -124,19 +124,19 @@ import {
124
124
  writeAdsJson,
125
125
  writeAdsOutput,
126
126
  writeJsonEnvelope
127
- } from "./chunk-X37GPJ54.js";
127
+ } from "./chunk-VKWHVMMD.js";
128
128
  import {
129
129
  ApiError,
130
130
  apiGet,
131
131
  apiGetWithHeaders,
132
132
  apiPost,
133
133
  validateConvexId
134
- } from "./chunk-WQE4SRLY.js";
134
+ } from "./chunk-S5BY45H7.js";
135
135
  import {
136
136
  installExitRecord,
137
137
  installStreamTaps,
138
138
  logInvocation
139
- } from "./chunk-5X5LUMVV.js";
139
+ } from "./chunk-ISJOVAJP.js";
140
140
  import {
141
141
  __commonJS,
142
142
  __esm,
@@ -171,7 +171,7 @@ import {
171
171
  winningAdNormalizer,
172
172
  writeJson,
173
173
  writeOutput
174
- } from "./chunk-N72QYDJY.js";
174
+ } from "./chunk-ISCXDKWB.js";
175
175
 
176
176
  // ../../node_modules/.pnpm/i18n-iso-countries@7.14.0/node_modules/i18n-iso-countries/langs/af.json
177
177
  var require_af = __commonJS({
@@ -21715,7 +21715,7 @@ var require_entry_node = __commonJS({
21715
21715
  });
21716
21716
 
21717
21717
  // src/cli.ts
21718
- import { defineCommand as defineCommand280, runMain } from "citty";
21718
+ import { defineCommand as defineCommand278, runMain } from "citty";
21719
21719
 
21720
21720
  // src/cache-flag.ts
21721
21721
  var NO_CACHE_ARG = {
@@ -22118,6 +22118,7 @@ var validatedHardFiltersSchema = winnersHardFiltersSchema.extend({
22118
22118
  first_seen_after: firstSeenBoundSchema.optional(),
22119
22119
  first_seen_before: firstSeenBoundSchema.optional()
22120
22120
  });
22121
+ var winnersSortSchema = z2.enum(["winner", "longest_running", "newest"]);
22121
22122
  var winnersSearchFields = z2.object({
22122
22123
  free_text_query: z2.string().max(500).optional(),
22123
22124
  ref_ad_id: z2.string().optional(),
@@ -22133,6 +22134,10 @@ var winnersSearchFields = z2.object({
22133
22134
  // One page picked for variety (different brands, containers and hooks)
22134
22135
  // instead of rank order: a reference set for N concepts. Ignores offset.
22135
22136
  diverse: z2.boolean().optional(),
22137
+ // The order of a browse (no query): by winner score (default), by how long
22138
+ // the ad has run, or by when it was first seen. A query ranks by relevance
22139
+ // blended with winner score, so `sort` only means something without one.
22140
+ sort: winnersSortSchema.optional(),
22136
22141
  hard_filters: winnersHardFiltersSchema.optional(),
22137
22142
  // `score_breakdown` surfaces the "why it ranked" panel; facets drive the
22138
22143
  // filter rail. thumbnails/full_dna_url are opt-in extras.
@@ -22140,9 +22145,11 @@ var winnersSearchFields = z2.object({
22140
22145
  });
22141
22146
  var hasSearchInput = (b) => b.free_text_query !== void 0 || b.ref_ad_id !== void 0 || b.hard_filters !== void 0;
22142
22147
  var SEARCH_INPUT_MESSAGE = "Provide free_text_query, ref_ad_id, or hard_filters";
22143
- var winnersSearchRequestSchema = winnersSearchFields.refine(hasSearchInput, { message: SEARCH_INPUT_MESSAGE });
22144
22148
  var winnerSearchScopeSchema = z2.enum(["all", "followed", "followed-first"]);
22145
- var scopedWinnersSearchRequestSchema = winnersSearchFields.extend({ scope: winnerSearchScopeSchema.optional(), hard_filters: validatedHardFiltersSchema.optional() }).refine(hasSearchInput, { message: SEARCH_INPUT_MESSAGE }).refine((b) => b.scope !== "followed-first" || !b.offset, {
22149
+ var scopedWinnersSearchRequestSchema = winnersSearchFields.extend({
22150
+ scope: winnerSearchScopeSchema.default("followed-first"),
22151
+ hard_filters: validatedHardFiltersSchema.optional()
22152
+ }).refine(hasSearchInput, { message: SEARCH_INPUT_MESSAGE }).refine((b) => b.scope !== "followed-first" || !b.offset, {
22146
22153
  message: "offset is not supported with scope followed-first; raise limit instead"
22147
22154
  });
22148
22155
  var winningAdDnaSchema = z2.object({
@@ -22231,7 +22238,12 @@ var winnersSearchResponseSchema = z2.object({
22231
22238
  country_coverage: z2.array(z2.object({ code: z2.string(), ads: z2.number() })).nullable()
22232
22239
  });
22233
22240
  var winnerSourceSchema = z2.enum(["followed", "library"]);
22234
- var referenceUsesSchema = z2.object({ companies: z2.number(), ours: z2.boolean() });
22241
+ var referenceUsesSchema = z2.object({
22242
+ companies: z2.number(),
22243
+ ours: z2.boolean(),
22244
+ approved: z2.number(),
22245
+ rejected: z2.number()
22246
+ });
22235
22247
  var scopedWinningAdSchema = winningAdSchema.extend({
22236
22248
  source: winnerSourceSchema,
22237
22249
  baker_uses: referenceUsesSchema.optional()
@@ -22277,10 +22289,6 @@ var advertiserConvergenceSchema = z2.object({
22277
22289
  iteration_velocity_30d: z2.number().nullable(),
22278
22290
  last_refresh_completed_at: z2.string().nullable()
22279
22291
  });
22280
- var resolvePreviewRequestSchema = z2.object({
22281
- input: z2.string().min(1),
22282
- platform: adLibraryPlatformSchema
22283
- });
22284
22292
  var resolvePreviewSchema = z2.object({
22285
22293
  platform: adLibraryPlatformSchema,
22286
22294
  external_id: z2.string(),
@@ -22365,11 +22373,6 @@ var unfollowRequestSchema = z2.object({
22365
22373
  advertiser_id: z2.string().optional()
22366
22374
  });
22367
22375
  var unfollowResponseSchema = z2.object({ removed: z2.boolean() });
22368
- var advertiserWinnersRequestSchema = z2.object({
22369
- advertiser_id: z2.string().min(1),
22370
- top: z2.number().int().min(1).max(100).optional(),
22371
- platform: adLibraryPlatformSchema.optional()
22372
- });
22373
22376
  var advertiserWinnersResponseSchema = z2.object({
22374
22377
  advertiser_id: z2.string(),
22375
22378
  winners: z2.array(winningAdSchema)
@@ -22389,7 +22392,9 @@ var feedRequestSchema = z2.object({
22389
22392
  limit: z2.number().int().min(1).max(200).optional()
22390
22393
  });
22391
22394
  var feedResponseSchema = z2.object({
22392
- winners: z2.array(winningAdSchema),
22395
+ // The feed only ever reads followed brands, so every winner is `followed`:
22396
+ // the same `source` a scoped search carries, so the surfaces agree.
22397
+ winners: z2.array(winningAdSchema.extend({ source: winnerSourceSchema })),
22393
22398
  // How many brands the company follows (regardless of the trim).
22394
22399
  following_count: z2.number(),
22395
22400
  // The advertiser ids actually queried (follows ∩ requested subset).
@@ -22398,39 +22403,6 @@ var feedResponseSchema = z2.object({
22398
22403
  // followed them yet.
22399
22404
  unknown_requested: z2.array(z2.string())
22400
22405
  });
22401
- var briefRequestSchema = z2.object({
22402
- // Partial DNA describing the target creative (all optional); the service
22403
- // pulls its own strategically-similar references.
22404
- target_dna: z2.record(z2.string(), z2.unknown()).optional(),
22405
- k_reference_ads: z2.number().int().min(1).max(50).optional(),
22406
- notes: z2.string().optional()
22407
- });
22408
- var briefResponseSchema = z2.object({
22409
- brief_markdown: z2.string(),
22410
- reference_ad_ids: z2.array(z2.string())
22411
- });
22412
- var cohortSelectorSchema = z2.object({
22413
- hard_filters: winnersHardFiltersSchema.optional(),
22414
- ad_ids: z2.array(z2.string()).optional()
22415
- });
22416
- var patternsRequestSchema = z2.object({
22417
- cohort_a: cohortSelectorSchema,
22418
- cohort_b: cohortSelectorSchema,
22419
- top_n: z2.number().int().min(1).max(50).optional()
22420
- });
22421
- var patternDiscriminatorSchema = z2.object({
22422
- field: z2.string(),
22423
- method: z2.string(),
22424
- score: z2.number(),
22425
- direction: z2.enum(["favors_a", "favors_b"]),
22426
- top_values_a: z2.array(z2.string()).nullable(),
22427
- top_values_b: z2.array(z2.string()).nullable()
22428
- });
22429
- var patternsResponseSchema = z2.object({
22430
- cohort_a_size: z2.number(),
22431
- cohort_b_size: z2.number(),
22432
- discriminators: z2.array(patternDiscriminatorSchema)
22433
- });
22434
22406
  var seedCatalogFieldSchema = z2.enum(["hook_archetype", "mechanism_type", "static_format_pattern"]);
22435
22407
  var seedCatalogRequestSchema = z2.object({
22436
22408
  field: seedCatalogFieldSchema,
@@ -35334,8 +35306,8 @@ var MEMBER_ARGS = {
35334
35306
  },
35335
35307
  country: { type: "string", description: "Country of phones written without a +code, e.g. ES" }
35336
35308
  };
35337
- function membersFromArgs(args8, readFile42 = (path59) => readFileSync2(path59, "utf8")) {
35338
- const fromFile = typeof args8.file === "string" && args8.file ? membersFromFile(readFile42(args8.file)) : { emails: [], phones: [] };
35309
+ function membersFromArgs(args8, readFile43 = (path59) => readFileSync2(path59, "utf8")) {
35310
+ const fromFile = typeof args8.file === "string" && args8.file ? membersFromFile(readFile43(args8.file)) : { emails: [], phones: [] };
35339
35311
  const given = [...csvArg(args8.emails) ?? [], ...fromFile.emails];
35340
35312
  const givenPhones = [...csvArg(args8.phones) ?? [], ...fromFile.phones];
35341
35313
  const members = hashedMembers({
@@ -53530,10 +53502,10 @@ Example: baker ads meta ads retag 120252158038400247 --url-tags "utm_source={{si
53530
53502
  }
53531
53503
  },
53532
53504
  run: async ({ args: args8 }) => {
53533
- const { apiPost: apiPost2 } = await import("./client-BPKOHQWA.js");
53534
- const { requireChatId: requireChatId2 } = await import("./env-B7V77K6K.js");
53535
- const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-5M2QPMKZ.js");
53536
- const { handleMetaError: handleMetaError2 } = await import("./shared-OEQKSDWO.js");
53505
+ const { apiPost: apiPost2 } = await import("./client-AY6TPFQ4.js");
53506
+ const { requireChatId: requireChatId2 } = await import("./env-CWEGTJF3.js");
53507
+ const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-EPF3HHJ4.js");
53508
+ const { handleMetaError: handleMetaError2 } = await import("./shared-53G34I3U.js");
53537
53509
  try {
53538
53510
  const accountId = bareAccountId2(args8);
53539
53511
  const urlTags = typeof args8["url-tags"] === "string" ? args8["url-tags"] : void 0;
@@ -53684,10 +53656,10 @@ function duplicateCommand2(entity, label) {
53684
53656
  replace: { type: "boolean", description: "Pause the original once the copy publishes" }
53685
53657
  },
53686
53658
  run: async ({ args: args8 }) => {
53687
- const { apiPost: apiPost2 } = await import("./client-BPKOHQWA.js");
53688
- const { requireChatId: requireChatId2 } = await import("./env-B7V77K6K.js");
53689
- const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-5M2QPMKZ.js");
53690
- const { handleMetaError: handleMetaError2 } = await import("./shared-OEQKSDWO.js");
53659
+ const { apiPost: apiPost2 } = await import("./client-AY6TPFQ4.js");
53660
+ const { requireChatId: requireChatId2 } = await import("./env-CWEGTJF3.js");
53661
+ const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-EPF3HHJ4.js");
53662
+ const { handleMetaError: handleMetaError2 } = await import("./shared-53G34I3U.js");
53691
53663
  try {
53692
53664
  const accountId = bareAccountId2(args8);
53693
53665
  const chatId = requireChatId2();
@@ -71574,7 +71546,7 @@ var rerunCommand = defineCommand132({
71574
71546
  });
71575
71547
 
71576
71548
  // src/commands/canvas/scaffold-static-ad.ts
71577
- import { mkdir as mkdir8, readFile as readFile23, writeFile as writeFile12 } from "fs/promises";
71549
+ import { mkdir as mkdir8, readFile as readFile24, writeFile as writeFile12 } from "fs/promises";
71578
71550
  import path31 from "path";
71579
71551
  import { defineCommand as defineCommand134 } from "citty";
71580
71552
 
@@ -71915,6 +71887,7 @@ function fileExists(target2) {
71915
71887
 
71916
71888
  // src/commands/canvas/creative-definition.ts
71917
71889
  import path28 from "path";
71890
+ import { parseDocument as parseDocument2 } from "yaml";
71918
71891
  var PLATFORM_VALUES = ["meta", "google", "linkedin", "tiktok", "youtube", "x", "other"];
71919
71892
  var FORMAT_VALUES = ["1:1", "4:5", "9:16", "16:9", "1.91:1"];
71920
71893
  function titleFromSlug(slug) {
@@ -71953,6 +71926,9 @@ function buildCreativeDefinition(input) {
71953
71926
  lines.push(`status: ${input.status ?? "draft"}`);
71954
71927
  if (input.sourceReferenceUrl) lines.push(`sourceReferenceUrl: ${yamlScalar(input.sourceReferenceUrl)}`);
71955
71928
  if (input.sourceAdvertiser) lines.push(`sourceAdvertiser: ${yamlScalar(input.sourceAdvertiser)}`);
71929
+ if (input.sourceAdvertiserId) lines.push(`sourceAdvertiserId: ${yamlScalar(input.sourceAdvertiserId)}`);
71930
+ if (input.sourceAdId) lines.push(`sourceAdId: ${yamlScalar(input.sourceAdId)}`);
71931
+ if (input.sourcePlatform) lines.push(`sourcePlatform: ${input.sourcePlatform}`);
71956
71932
  if (input.sourceKind) lines.push(`sourceKind: ${input.sourceKind}`);
71957
71933
  if (input.sourcePath) lines.push(`sourcePath: ${yamlScalar(input.sourcePath)}`);
71958
71934
  lines.push("---", "");
@@ -71960,6 +71936,19 @@ function buildCreativeDefinition(input) {
71960
71936
  lines.push("");
71961
71937
  return lines.join("\n");
71962
71938
  }
71939
+ var SOURCE_FIELD_KEYS = ["sourceAdId", "sourcePlatform", "sourceAdvertiserId"];
71940
+ function parseDefinitionSourceFields(raw) {
71941
+ const match = raw.match(/^---\r?\n([\s\S]*?\r?\n)?---/);
71942
+ if (!match) return {};
71943
+ const doc = parseDocument2(match[1] ?? "");
71944
+ if (doc.errors.length > 0) return {};
71945
+ const fields = {};
71946
+ for (const key of SOURCE_FIELD_KEYS) {
71947
+ const value2 = doc.get(key);
71948
+ if (typeof value2 === "string" && value2.trim()) fields[key] = value2.trim();
71949
+ }
71950
+ return fields;
71951
+ }
71963
71952
 
71964
71953
  // src/commands/canvas/scaffold-static-ad-paths.ts
71965
71954
  import path29 from "path";
@@ -71981,7 +71970,7 @@ function isValidScaffoldSlug(slug) {
71981
71970
  }
71982
71971
 
71983
71972
  // src/commands/canvas/sync-definition.ts
71984
- import { readdir as readdir11, stat as stat4 } from "fs/promises";
71973
+ import { readdir as readdir11, readFile as readFile23, stat as stat4 } from "fs/promises";
71985
71974
  import path30 from "path";
71986
71975
  import { defineCommand as defineCommand133 } from "citty";
71987
71976
 
@@ -72029,6 +72018,21 @@ function canvasToDefinitionGraph(canvas) {
72029
72018
  }
72030
72019
 
72031
72020
  // src/commands/canvas/sync-definition.ts
72021
+ function buildDefinitionSyncPayload(input, graph, chatId) {
72022
+ const { canvas: _canvas, ...fields } = input;
72023
+ return Object.fromEntries([
72024
+ ...Object.entries(fields).filter(([, v]) => v !== void 0),
72025
+ ["graph", graph],
72026
+ ["chatId", chatId]
72027
+ ]);
72028
+ }
72029
+ async function readDefinitionSourceFields(canvasPath) {
72030
+ try {
72031
+ return parseDefinitionSourceFields(await readFile23(path30.join(path30.dirname(canvasPath), "_definition.md"), "utf8"));
72032
+ } catch {
72033
+ return {};
72034
+ }
72035
+ }
72032
72036
  async function syncCreativeDefinitionBestEffort(input) {
72033
72037
  const chatId = process.env.BAKER_CHAT_ID;
72034
72038
  if (!chatId) return;
@@ -72037,16 +72041,7 @@ async function syncCreativeDefinitionBestEffort(input) {
72037
72041
  try {
72038
72042
  const creds = requireCredentialsFromEnv();
72039
72043
  const client = new BackendClient2({ baseUrl: creds.url, apiKey: creds.apiKey });
72040
- await client.syncCreativeDefinition({
72041
- slug: input.slug,
72042
- title: input.title,
72043
- platform: input.platform,
72044
- formats: input.formats,
72045
- sourceReferenceUrl: input.sourceReferenceUrl,
72046
- graph,
72047
- sourceSha: input.sourceSha,
72048
- chatId
72049
- });
72044
+ await client.syncCreativeDefinition(buildDefinitionSyncPayload(input, graph, chatId));
72050
72045
  process.stdout.write(`[definition] synced workflow graph (${graph.nodes.length} nodes) \u2014 view it in the dashboard
72051
72046
  `);
72052
72047
  } catch (e) {
@@ -72105,7 +72100,7 @@ var syncDefinitionCommand = defineCommand133({
72105
72100
  } catch {
72106
72101
  return;
72107
72102
  }
72108
- await syncCreativeDefinitionBestEffort({ slug, canvas, sourceSha });
72103
+ await syncCreativeDefinitionBestEffort({ slug, canvas, sourceSha, ...await readDefinitionSourceFields(canvasPath) });
72109
72104
  }
72110
72105
  });
72111
72106
 
@@ -72138,6 +72133,117 @@ async function resolveCastAvatars(flag) {
72138
72133
  return { handles, avatars };
72139
72134
  }
72140
72135
 
72136
+ // src/commands/canvas/library-reference.ts
72137
+ var LIBRARY_PLATFORMS = ["meta", "linkedin", "tiktok"];
72138
+ function resolveSourcePlatform(flag) {
72139
+ const value2 = flag?.trim();
72140
+ if (!value2) return { ok: true, platform: "meta" };
72141
+ if (LIBRARY_PLATFORMS.includes(value2)) return { ok: true, platform: value2 };
72142
+ return {
72143
+ ok: false,
72144
+ message: `--source-platform "${value2}" is not a library platform. Use one of: ${LIBRARY_PLATFORMS.join(", ")}.`
72145
+ };
72146
+ }
72147
+ async function fetchLibraryReference(adId, platform) {
72148
+ const data = await apiGet("/api/ad-library/ad-content", { id: adId, platform });
72149
+ if (!data?.ad) throw new Error(`the library returned no ad for ${adId} on ${platform}`);
72150
+ return data.ad;
72151
+ }
72152
+ function filled(entries) {
72153
+ return Object.fromEntries(
72154
+ Object.entries(entries).filter(([, v]) => v !== null && v !== void 0 && !(Array.isArray(v) && v.length === 0))
72155
+ );
72156
+ }
72157
+ function libraryReferenceBlock(ad, ids2) {
72158
+ const textBlocks = ad.on_screen_text.flatMap(
72159
+ (o) => o.text ? [{ text: o.text, ...o.role ? { role: o.role } : {}, ...o.position ? { position: o.position } : {} }] : []
72160
+ );
72161
+ const advertiser = ad.advertiser ?? void 0;
72162
+ const advertiserId = ids2.advertiserId ?? ad.advertiser_id ?? void 0;
72163
+ return {
72164
+ ad_id: ids2.adId,
72165
+ platform: ids2.platform,
72166
+ ...advertiser ? { advertiser } : {},
72167
+ ...advertiserId ? { advertiser_id: advertiserId } : {},
72168
+ ...ad.layout.format_pattern ? { format_pattern: ad.layout.format_pattern } : {},
72169
+ layout: filled({
72170
+ composition: ad.layout.composition,
72171
+ focal_subject: ad.layout.focal_subject,
72172
+ aspect_ratio: ad.layout.aspect_ratio,
72173
+ background: ad.layout.background,
72174
+ palette_mood: ad.layout.palette_mood,
72175
+ colors: ad.layout.colors,
72176
+ production: ad.layout.production,
72177
+ aesthetic: ad.layout.aesthetic,
72178
+ cta_in_image: ad.layout.cta_in_image,
72179
+ markers: ad.layout.markers
72180
+ }),
72181
+ message: filled({
72182
+ mechanism: ad.message.mechanism,
72183
+ awareness: ad.message.awareness_stage,
72184
+ sophistication: ad.message.sophistication,
72185
+ angle: ad.angle,
72186
+ hook_archetype: ad.hook_archetype,
72187
+ pain_points: ad.message.pain_points,
72188
+ desires: ad.message.desires,
72189
+ value_props: ad.message.value_props,
72190
+ objections_handled: ad.message.objections_handled,
72191
+ proof: ad.message.proof,
72192
+ risk_reversal: ad.message.risk_reversal
72193
+ }),
72194
+ offer: filled({
72195
+ offer_type: ad.offer.offer_type,
72196
+ pricing_model: ad.offer.pricing_model,
72197
+ entry_price: ad.offer.entry_price,
72198
+ no_credit_card: ad.offer.no_credit_card,
72199
+ guarantee: ad.offer.guarantee,
72200
+ guarantee_days: ad.offer.guarantee_days,
72201
+ urgency: ad.offer.urgency,
72202
+ bonus: ad.offer.bonus
72203
+ }),
72204
+ text_blocks: textBlocks,
72205
+ copy: filled({ primary_text: ad.primary_text, headline: ad.headline, cta: ad.cta })
72206
+ };
72207
+ }
72208
+ function referenceDescribeContext(reference, agentContext) {
72209
+ const own = agentContext?.trim() || void 0;
72210
+ if (!reference) return own;
72211
+ const facts = [];
72212
+ if (reference.advertiser) facts.push(`advertiser: ${reference.advertiser}`);
72213
+ if (reference.format_pattern) facts.push(`container (format_pattern): ${reference.format_pattern}`);
72214
+ if (typeof reference.layout.composition === "string") facts.push(`composition: ${reference.layout.composition}`);
72215
+ if (typeof reference.message.mechanism === "string") facts.push(`persuasion mechanism: ${reference.message.mechanism}`);
72216
+ if (typeof reference.message.awareness === "string") facts.push(`awareness stage: ${reference.message.awareness}`);
72217
+ if (typeof reference.offer.offer_type === "string") facts.push(`offer: ${reference.offer.offer_type}`);
72218
+ if (reference.text_blocks.length > 0) {
72219
+ facts.push(
72220
+ `text blocks: ${reference.text_blocks.map((b) => `"${b.text}"${b.role ? ` (${b.role})` : ""}`).join("; ")}`
72221
+ );
72222
+ }
72223
+ const library = `The ads library has already read this ad: ${facts.join(". ")}. Your blueprint must agree with it on the container and the mechanism; refine and detail them, never contradict them, and transcribe the text blocks exactly.`;
72224
+ return own ? `${own}. ${library}` : library;
72225
+ }
72226
+ function mergeReferenceIntoBlueprint(blueprint, reference) {
72227
+ if (!reference || !blueprint || typeof blueprint !== "object" || Array.isArray(blueprint)) return blueprint;
72228
+ const merged = { ...blueprint, reference };
72229
+ if (winningMechanisms(blueprint).length === 0) {
72230
+ const container = reference.format_pattern;
72231
+ const mechanism = typeof reference.message.mechanism === "string" ? reference.message.mechanism : void 0;
72232
+ const device = container && mechanism ? `${container} container carrying ${mechanism}` : container && `${container} container` || mechanism && `${mechanism} persuasion mechanism`;
72233
+ if (device) {
72234
+ merged.winning_mechanisms = [
72235
+ {
72236
+ kind: "structural",
72237
+ device,
72238
+ why_it_works: "The winners library recorded this ad as a winner on this container and persuasion mechanism.",
72239
+ source: "library"
72240
+ }
72241
+ ];
72242
+ }
72243
+ }
72244
+ return merged;
72245
+ }
72246
+
72141
72247
  // src/commands/canvas/scaffold-static-ad.ts
72142
72248
  function toCanvasRelative2(canvasDir, file) {
72143
72249
  const rel = path31.relative(canvasDir, file).split(path31.sep).join("/");
@@ -72152,7 +72258,7 @@ async function uploadSourceAsReference(source, isUrl, client) {
72152
72258
  throw new Error(`failed to download source image (${e instanceof Error ? e.message : String(e)})`);
72153
72259
  }
72154
72260
  } else {
72155
- bytes = await readFile23(source);
72261
+ bytes = await readFile24(source);
72156
72262
  }
72157
72263
  const safe = await toModelSafeImage(bytes);
72158
72264
  const sha2562 = sha256Hex(safe.bytes);
@@ -72292,13 +72398,102 @@ async function runVisionPasses(canvas) {
72292
72398
  return fail4("read_outputs", e instanceof Error ? e.message : String(e));
72293
72399
  }
72294
72400
  }
72401
+ var SOURCE_AD_ID_DESCRIPTION = "The winners-library ad this creative adapts (the `ad_id` from `baker winning-ads search`). The library's read of that ad (container, layout, colours, text blocks with their roles, persuasion mechanism, offer) is fetched and written into prompt.json as `reference`, the describe pass is told it so the two reads agree, and the source is recorded on the creative from this scaffold on. With it, the image is optional: the library's picture is used when none is given. Pass it whenever the reference came from the library.";
72402
+ var SOURCE_PLATFORM_DESCRIPTION = `Which platform the library ad is on: ${LIBRARY_PLATFORMS.join("|")} (default meta).`;
72403
+ var SOURCE_ADVERTISER_ID_DESCRIPTION = "The library advertiser id of the reference (the `advertiser_id` beside the ad in search results). Defaults to the one the library returns for the ad.";
72404
+ registerSchema({
72405
+ command: "canvas.scaffold-static-ad",
72406
+ description: "Turn a reference image into a runnable static-ad canvas: billed image_describe + element-selection passes, then a canvas on which the image model renders the finished ad from prompt.json. Start here for a static ad adapted from a winning ad: pass `--source-ad-id <ad_id> --source-platform <platform>` from the search result, so the library's structured read grounds the blueprint and the source is recorded on the creative; the image may then be omitted. `baker avatars list`, then `--avatar <handle>` when the image shows a person.",
72407
+ args: {
72408
+ file: {
72409
+ type: "positional",
72410
+ required: false,
72411
+ description: "Path or http(s) URL to the reference image. Optional with --source-ad-id: the library's picture is used."
72412
+ },
72413
+ "source-ad-id": { type: "string", required: false, description: SOURCE_AD_ID_DESCRIPTION },
72414
+ "source-platform": {
72415
+ type: "string",
72416
+ required: false,
72417
+ description: SOURCE_PLATFORM_DESCRIPTION,
72418
+ enum: [...LIBRARY_PLATFORMS]
72419
+ },
72420
+ "source-advertiser-id": { type: "string", required: false, description: SOURCE_ADVERTISER_ID_DESCRIPTION },
72421
+ context: {
72422
+ type: "string",
72423
+ required: false,
72424
+ description: "Known provenance (advertiser, category, market, their brand palette) to ground the describe pass"
72425
+ },
72426
+ slug: {
72427
+ type: "string",
72428
+ required: false,
72429
+ description: "Creative slug: writes src/creatives/<slug>/<slug>.canvas.json, prompt.json and _definition.md"
72430
+ },
72431
+ title: { type: "string", required: false, description: "Creative title for _definition.md (default: title-cased slug)" },
72432
+ platform: {
72433
+ type: "string",
72434
+ required: false,
72435
+ description: "Ad platform for _definition.md and the default placement set (meta|google|linkedin|tiktok|youtube|x|other; default meta)"
72436
+ },
72437
+ advertiser: {
72438
+ type: "string",
72439
+ required: false,
72440
+ description: "Source advertiser name for _definition.md (default: the library ad's advertiser when --source-ad-id is given)"
72441
+ },
72442
+ placements: {
72443
+ type: "string",
72444
+ required: false,
72445
+ description: "Formats delivered: meta_feed|meta_feed_stories|meta_all|linkedin|google_demandgen|tiktok|youtube, or none for a single base ad (default from --platform)"
72446
+ },
72447
+ aspect: { type: "string", required: false, description: "Force the lead format (else inferred from the image)" },
72448
+ takes: { type: "string", required: false, description: "Finished ads rendered and judged, 1-3 (default 3)" },
72449
+ avatar: {
72450
+ type: "string",
72451
+ required: false,
72452
+ description: "Cast company avatars as the ad's people, comma-separated in detection order (`marta` or `marta,joan`)"
72453
+ },
72454
+ "skip-actor-sheets": {
72455
+ type: "boolean",
72456
+ required: false,
72457
+ description: "Ground each animal hero on its lone dropped photo instead of a generated multi-view sheet"
72458
+ },
72459
+ out: { type: "string", required: false, description: "Output canvas path (default <image-dir>/static-ad.canvas.json)" },
72460
+ "describe-model": { type: "string", required: false, description: "Override the image_describe model id" },
72461
+ "select-model": { type: "string", required: false, description: "Override the element-selection text_generate model id" },
72462
+ "gen-model": { type: "string", required: false, description: "Override the image_generate model id" }
72463
+ }
72464
+ });
72465
+ async function lookupReference(args8) {
72466
+ const adId = args8["source-ad-id"] ? String(args8["source-ad-id"]).trim() : "";
72467
+ if (!adId) return { status: "none" };
72468
+ const platformResult = resolveSourcePlatform(args8["source-platform"] ? String(args8["source-platform"]) : void 0);
72469
+ if (!platformResult.ok) return fail4("invalid_source_platform", platformResult.message);
72470
+ const platform = platformResult.platform;
72471
+ const advertiserId = args8["source-advertiser-id"] ? String(args8["source-advertiser-id"]).trim() : void 0;
72472
+ try {
72473
+ const ad = await fetchLibraryReference(adId, platform);
72474
+ return {
72475
+ status: "grounded",
72476
+ reference: libraryReferenceBlock(ad, { adId, platform, advertiserId }),
72477
+ mediaUrl: ad.media_url ?? void 0
72478
+ };
72479
+ } catch (e) {
72480
+ return { status: "unavailable", adId, platform, message: e instanceof Error ? e.message : String(e) };
72481
+ }
72482
+ }
72295
72483
  var scaffoldStaticAdCommand = defineCommand134({
72296
72484
  meta: {
72297
72485
  name: "scaffold-static-ad",
72298
- description: "Turn a source/inspiration image into a runnable static-ad canvas. Runs billed passes \u2014 image_describe (the blueprint, baked to prompt.json as the editable 'prompt') and an AI selection of the image's MAIN identity elements \u2014 then scaffolds a canvas on which the image model renders the FINISHED ad: up to three candidates from prompt.json, the copy quoted verbatim, the brand's typeface shown as a specimen and the logo from src/brand/logos/ as a reference (only when the concept shows one), judged on one idea, exact words, layout, mechanism, brand and artifacts; every other placement format is the winner re-laid by the model, and a format the model has no shape for is cropped exact. The run reads every word back and refuses to call the ad ready when one is off. No copy means the picture is the ad. Pass --placements none for a single base ad, or a preset to pick the set. Edit prompt.json and drop the real assets, then `baker canvas run` it. Start here: `baker avatars list`, then pass `--avatar <handle>` when the image shows a person \u2014 the ad's people are played by the company's avatars, never by a photo of anyone real."
72486
+ description: "Turn a source/inspiration image into a runnable static-ad canvas. Runs billed passes \u2014 image_describe (the blueprint, baked to prompt.json as the editable 'prompt') and an AI selection of the image's MAIN identity elements \u2014 then scaffolds a canvas on which the image model renders the FINISHED ad: up to three candidates from prompt.json, the copy quoted verbatim, the brand's typeface shown as a specimen and the logo from src/brand/logos/ as a reference (only when the concept shows one), judged on one idea, exact words, layout, mechanism, brand and artifacts; every other placement format is the winner re-laid by the model, and a format the model has no shape for is cropped exact. The run reads every word back and refuses to call the ad ready when one is off. No copy means the picture is the ad. Pass --placements none for a single base ad, or a preset to pick the set. Edit prompt.json and drop the real assets, then `baker canvas run` it. Start here: `baker avatars list`, then pass `--avatar <handle>` when the image shows a person \u2014 the ad's people are played by the company's avatars, never by a photo of anyone real. A reference from the library: pass `--source-ad-id <ad_id> --source-platform <platform>` so its structured read grounds the blueprint and the source is recorded on the creative."
72299
72487
  },
72300
72488
  args: {
72301
- file: { type: "positional", required: true, description: "Path or http(s) URL to the source/inspiration image" },
72489
+ file: {
72490
+ type: "positional",
72491
+ required: false,
72492
+ description: "Path or http(s) URL to the source/inspiration image (optional with --source-ad-id)"
72493
+ },
72494
+ "source-ad-id": { type: "string", description: SOURCE_AD_ID_DESCRIPTION },
72495
+ "source-platform": { type: "string", description: SOURCE_PLATFORM_DESCRIPTION },
72496
+ "source-advertiser-id": { type: "string", description: SOURCE_ADVERTISER_ID_DESCRIPTION },
72302
72497
  context: { type: "string", description: "Known provenance (advertiser, category, market) to ground the describe" },
72303
72498
  out: { type: "string", description: "Output canvas path (default <image-dir>/static-ad.canvas.json)" },
72304
72499
  slug: {
@@ -72310,7 +72505,10 @@ var scaffoldStaticAdCommand = defineCommand134({
72310
72505
  type: "string",
72311
72506
  description: "Ad platform (meta|google|linkedin|tiktok|youtube|x|other; default meta). Sets _definition.md AND, unless --placements is given, the formats the ad is delivered in (meta \u2192 4:5+9:16, google \u2192 1:1+4:5+16:9, linkedin \u2192 1:1, tiktok \u2192 9:16, youtube \u2192 16:9; x/other \u2192 one format)."
72312
72507
  },
72313
- advertiser: { type: "string", description: "Source advertiser recorded in _definition.md" },
72508
+ advertiser: {
72509
+ type: "string",
72510
+ description: "Source advertiser recorded in _definition.md (default: the library ad's advertiser)"
72511
+ },
72314
72512
  "describe-model": { type: "string", description: "Override the image_describe model id" },
72315
72513
  "select-model": { type: "string", description: "Override the text_generate model id for element selection" },
72316
72514
  "gen-model": { type: "string", description: "Override the image_generate model id" },
@@ -72344,12 +72542,22 @@ var scaffoldStaticAdCommand = defineCommand134({
72344
72542
  );
72345
72543
  process.exit(2);
72346
72544
  }
72347
- const { imageIsUrl, imageSource, outPath, blueprintPath: blueprintPath2, definitionPath, referencesDir } = resolveScaffoldStaticAdPaths(
72348
- String(args8.file),
72349
- args8.out ? String(args8.out) : void 0,
72350
- process.cwd(),
72351
- slug
72352
- );
72545
+ const lookup = await lookupReference(args8);
72546
+ if (lookup.status === "unavailable") {
72547
+ process.stderr.write(
72548
+ `[warn] winners-library ad ${lookup.adId} (${lookup.platform}) could not be read (${lookup.message}); scaffolding without its structured read
72549
+ `
72550
+ );
72551
+ }
72552
+ const reference = lookup.status === "grounded" ? lookup.reference : void 0;
72553
+ const fileArg2 = args8.file ? String(args8.file) : void 0;
72554
+ const sourceFile = fileArg2 ?? (lookup.status === "grounded" ? lookup.mediaUrl : void 0);
72555
+ if (!sourceFile) {
72556
+ if (lookup.status === "none") return fail4("missing_source", "Pass the reference image, or --source-ad-id <ad_id> to use a library ad.");
72557
+ if (lookup.status === "unavailable") return fail4("source_unavailable", `No image given and the library ad could not be read (${lookup.message}).`);
72558
+ return fail4("source_unavailable", `The library holds no picture for ad ${reference?.ad_id}; pass the image.`);
72559
+ }
72560
+ const { imageIsUrl, imageSource, outPath, blueprintPath: blueprintPath2, definitionPath, referencesDir } = resolveScaffoldStaticAdPaths(sourceFile, args8.out ? String(args8.out) : void 0, process.cwd(), slug);
72353
72561
  if (slug) await enforceNewCreativeLimit(slug, outPath);
72354
72562
  await mkdir8(path31.dirname(outPath), { recursive: true });
72355
72563
  let models;
@@ -72377,10 +72585,11 @@ var scaffoldStaticAdCommand = defineCommand134({
72377
72585
  durableSourceUrl !== void 0 || imageIsUrl,
72378
72586
  describeModel,
72379
72587
  selectModel,
72380
- args8.context ? String(args8.context) : void 0
72588
+ referenceDescribeContext(reference, args8.context ? String(args8.context) : void 0)
72381
72589
  );
72382
72590
  const vision = await runVisionPasses(describeCanvas);
72383
- const { blueprint, creditsSpent } = vision;
72591
+ const { creditsSpent } = vision;
72592
+ const blueprint = mergeReferenceIntoBlueprint(vision.blueprint, reference);
72384
72593
  const elements = castAvatarsOnStaticElements(vision.elements, cast.avatars);
72385
72594
  const uncast = staticAvatarsNotCast(vision.elements, cast.handles);
72386
72595
  const canvasDir = path31.dirname(outPath);
@@ -72441,6 +72650,15 @@ var scaffoldStaticAdCommand = defineCommand134({
72441
72650
  }
72442
72651
  await writeFile12(outPath, `${JSON.stringify(canvas, null, 2)}
72443
72652
  `, "utf8");
72653
+ const sourceFields = reference ? {
72654
+ sourceAdId: reference.ad_id,
72655
+ sourcePlatform: reference.platform,
72656
+ ...reference.advertiser_id ? { sourceAdvertiserId: reference.advertiser_id } : {}
72657
+ } : lookup.status === "unavailable" ? {
72658
+ sourceAdId: lookup.adId,
72659
+ sourcePlatform: lookup.platform,
72660
+ ...args8["source-advertiser-id"] ? { sourceAdvertiserId: String(args8["source-advertiser-id"]).trim() } : {}
72661
+ } : {};
72444
72662
  if (definitionPath && !await fileExists(definitionPath)) {
72445
72663
  await writeFile12(
72446
72664
  definitionPath,
@@ -72450,7 +72668,8 @@ var scaffoldStaticAdCommand = defineCommand134({
72450
72668
  platform: definitionPlatform,
72451
72669
  formats: resolveFormatList(report.output_formats),
72452
72670
  sourceReferenceUrl: durableSourceUrl ?? (imageIsUrl ? imageSource : void 0),
72453
- sourceAdvertiser: args8.advertiser ? String(args8.advertiser) : args8.context ? String(args8.context) : void 0,
72671
+ sourceAdvertiser: args8.advertiser ? String(args8.advertiser) : reference?.advertiser ?? (args8.context ? String(args8.context) : void 0),
72672
+ ...sourceFields,
72454
72673
  sourceKind: "image",
72455
72674
  description: describeBlueprintIntent(blueprint)
72456
72675
  }),
@@ -72464,11 +72683,15 @@ var scaffoldStaticAdCommand = defineCommand134({
72464
72683
  platform: definitionPlatform,
72465
72684
  formats: resolveFormatList(report.output_formats),
72466
72685
  sourceReferenceUrl: durableSourceUrl ?? (imageIsUrl ? imageSource : void 0),
72686
+ ...sourceFields,
72467
72687
  canvas,
72468
72688
  sourceSha: await computeSourceSha(outPath)
72469
72689
  });
72470
72690
  }
72471
72691
  const hints2 = [
72692
+ ...lookup.status === "unavailable" ? [
72693
+ `REFERENCE NOT READ: the winners library could not return ad ${lookup.adId} (${lookup.message}). The blueprint is the describe pass alone and prompt.json has no \`reference\` block; the source ids are still recorded. Check the ad id and platform, or continue from the picture.`
72694
+ ] : [],
72472
72695
  ...uncast.length ? [
72473
72696
  `NOT CAST: ${uncast.map((h) => `\`${h}\``).join(", ")} \u2014 the element selection found fewer people than avatars passed, so ${uncast.length === 1 ? "this avatar plays" : "these avatars play"} nobody in this ad. Tell the user who is cast and who is not.`
72474
72697
  ] : [],
@@ -72485,6 +72708,15 @@ var scaffoldStaticAdCommand = defineCommand134({
72485
72708
  prompt_path: blueprintPath2,
72486
72709
  definition_path: definitionPath ?? void 0,
72487
72710
  source_reference: durableSourceUrl ?? void 0,
72711
+ ...reference ? {
72712
+ reference: {
72713
+ ad_id: reference.ad_id,
72714
+ platform: reference.platform,
72715
+ advertiser_id: reference.advertiser_id,
72716
+ format_pattern: reference.format_pattern,
72717
+ mechanism: reference.message.mechanism
72718
+ }
72719
+ } : {},
72488
72720
  output: canvas.output,
72489
72721
  ...hints2.length ? { hints: hints2 } : {},
72490
72722
  models: { describe: describeModel, select: selectModel, gen: opts.genModel },
@@ -72497,7 +72729,7 @@ var scaffoldStaticAdCommand = defineCommand134({
72497
72729
  run_estimated_credits: validation.estimatedCredits
72498
72730
  },
72499
72731
  checklist: {
72500
- edit_prompt: `Edit ${path31.basename(blueprintPath2)} \u2014 it is the blueprint generated from your image; rewrite it into the ad you want (palette, copy, claims, subjects). It feeds the generator directly.`,
72732
+ edit_prompt: `Edit ${path31.basename(blueprintPath2)} \u2014 it is the blueprint generated from your image; rewrite it into the ad you want (palette, copy, claims, subjects). It feeds the generator directly.${reference ? " Its `reference` block is the library's read of the source ad (container, layout, mechanism, offer, text blocks): keep it as the account of why the ad won, and rebuild it for our brand." : ""}`,
72501
72733
  winning_mechanisms: report.winning_mechanisms,
72502
72734
  rebuild_the_mechanism: "`winning_mechanisms` is WHY the source ad works. Rewrite each one for our brand in prompt.json (a rhyme stays a rhyme in our words, a before/after stays a before/after of our product); never copy the source's literal copy.",
72503
72735
  assets_to_supply: report.elements,
@@ -72558,7 +72790,7 @@ function clientLogoMissNote(decision, domain) {
72558
72790
  }
72559
72791
 
72560
72792
  // src/commands/canvas/scaffold-ad.ts
72561
- import { copyFile, cp, mkdir as mkdir9, readFile as readFile25, stat as stat5, writeFile as writeFile14 } from "fs/promises";
72793
+ import { copyFile, cp, mkdir as mkdir9, readFile as readFile26, stat as stat5, writeFile as writeFile14 } from "fs/promises";
72562
72794
  import path33 from "path";
72563
72795
  import { defineCommand as defineCommand135 } from "citty";
72564
72796
 
@@ -73273,7 +73505,7 @@ function injectAdBrandOverlay(indexHtml, overlayHtml, style = {}) {
73273
73505
  }
73274
73506
 
73275
73507
  // src/commands/canvas/composition-dims.ts
73276
- import { readFile as readFile24, writeFile as writeFile13 } from "fs/promises";
73508
+ import { readFile as readFile25, writeFile as writeFile13 } from "fs/promises";
73277
73509
  import path32 from "path";
73278
73510
  function patchCompositionMeta(metaJson, dims) {
73279
73511
  try {
@@ -73290,10 +73522,10 @@ function patchCompositionHtml(html, dims) {
73290
73522
  }
73291
73523
  async function stampCompositionDims(compositionDir, dims) {
73292
73524
  const metaPath = path32.join(compositionDir, "meta.json");
73293
- const rawMeta = await readFile24(metaPath, "utf8");
73525
+ const rawMeta = await readFile25(metaPath, "utf8");
73294
73526
  await writeFile13(metaPath, patchCompositionMeta(rawMeta, dims), "utf8");
73295
73527
  const htmlPath = path32.join(compositionDir, "index.html");
73296
- const rawHtml = await readFile24(htmlPath, "utf8");
73528
+ const rawHtml = await readFile25(htmlPath, "utf8");
73297
73529
  await writeFile13(htmlPath, patchCompositionHtml(rawHtml, dims), "utf8");
73298
73530
  }
73299
73531
 
@@ -73376,7 +73608,7 @@ var scaffoldAdCommand = defineCommand135({
73376
73608
  const specPath = args8.spec;
73377
73609
  let parsed;
73378
73610
  try {
73379
- parsed = JSON.parse(await readFile25(specPath, "utf-8"));
73611
+ parsed = JSON.parse(await readFile26(specPath, "utf-8"));
73380
73612
  } catch (e) {
73381
73613
  writeJson({
73382
73614
  ok: false,
@@ -73537,7 +73769,7 @@ var scaffoldAdCommand = defineCommand135({
73537
73769
  ] : [];
73538
73770
  const markPath = spec.data.brand?.logo?.trim();
73539
73771
  if (!noBrand && markPath?.toLowerCase().endsWith(".svg")) {
73540
- const svg = await readFile25(markPath, "utf-8").catch(() => null);
73772
+ const svg = await readFile26(markPath, "utf-8").catch(() => null);
73541
73773
  if (svg === null) {
73542
73774
  writeJson({
73543
73775
  ok: false,
@@ -73617,10 +73849,10 @@ var scaffoldAdCommand = defineCommand135({
73617
73849
  await stampCompositionDims(compositionDest, outDims);
73618
73850
  await stampCompositionDims(captionsDest, outDims);
73619
73851
  const captionsIndex = path33.join(captionsDest, "index.html");
73620
- await writeFile14(captionsIndex, injectAdCaptionPlacement(await readFile25(captionsIndex, "utf-8"), outAr), "utf-8");
73852
+ await writeFile14(captionsIndex, injectAdCaptionPlacement(await readFile26(captionsIndex, "utf-8"), outAr), "utf-8");
73621
73853
  const face = await stageBrandDisplayFace(outDir, captionsDest);
73622
73854
  if (face) {
73623
- await writeFile14(captionsIndex, injectAdDisplayFace(await readFile25(captionsIndex, "utf-8"), face.file), "utf-8");
73855
+ await writeFile14(captionsIndex, injectAdDisplayFace(await readFile26(captionsIndex, "utf-8"), face.file), "utf-8");
73624
73856
  }
73625
73857
  const blueprintPath2 = path33.join(outDir, "prompt.json");
73626
73858
  const blueprintStylePath = path33.join(outDir, "prompt.style.json");
@@ -73713,7 +73945,7 @@ var scaffoldAdCommand = defineCommand135({
73713
73945
  }) : "";
73714
73946
  if (overlayHtml && renderedDir) {
73715
73947
  const indexPath = path33.join(renderedDir, "index.html");
73716
- const html = await readFile25(indexPath, "utf-8");
73948
+ const html = await readFile26(indexPath, "utf-8");
73717
73949
  await writeFile14(indexPath, injectAdBrandOverlay(html, overlayHtml, { aspect: outAr, ...face ? { face: AD_DISPLAY_FAMILY } : {} }), "utf-8");
73718
73950
  }
73719
73951
  await writeFile14(
@@ -73901,7 +74133,7 @@ async function stageBrandDisplayFace(outDir, compositionDir) {
73901
74133
  }
73902
74134
 
73903
74135
  // src/commands/canvas/scaffold-video.ts
73904
- import { cp as cp2, mkdir as mkdir10, readFile as readFile28, rm as rm8, writeFile as writeFile15 } from "fs/promises";
74136
+ import { cp as cp2, mkdir as mkdir10, readFile as readFile29, rm as rm8, writeFile as writeFile15 } from "fs/promises";
73905
74137
  import { tmpdir as tmpdir3 } from "os";
73906
74138
  import path35 from "path";
73907
74139
  import { defineCommand as defineCommand136 } from "citty";
@@ -73943,7 +74175,7 @@ function routeVideoModel(input) {
73943
74175
 
73944
74176
  // src/engine/nodes/local/lib/sceneDetect.ts
73945
74177
  import { execFile as execFile3 } from "child_process";
73946
- import { mkdtemp as mkdtemp2, readdir as readdir12, readFile as readFile26, rm as rm7 } from "fs/promises";
74178
+ import { mkdtemp as mkdtemp2, readdir as readdir12, readFile as readFile27, rm as rm7 } from "fs/promises";
73947
74179
  import { tmpdir as tmpdir2 } from "os";
73948
74180
  import { join as join3 } from "path";
73949
74181
  import { promisify as promisify3 } from "util";
@@ -74019,7 +74251,7 @@ async function runSceneDetectOnce(filePath, threshold, minSceneLenS, timeoutMs)
74019
74251
  );
74020
74252
  const csvName = (await readdir12(outDir)).find((f) => f.toLowerCase().endsWith(".csv"));
74021
74253
  if (!csvName) return [];
74022
- return parsePySceneDetectCsvCuts(await readFile26(join3(outDir, csvName), "utf-8"));
74254
+ return parsePySceneDetectCsvCuts(await readFile27(join3(outDir, csvName), "utf-8"));
74023
74255
  } finally {
74024
74256
  await rm7(outDir, { recursive: true, force: true });
74025
74257
  }
@@ -74043,7 +74275,7 @@ async function detectSceneCutsPySceneDetect(filePath, opts = {}) {
74043
74275
  }
74044
74276
 
74045
74277
  // src/commands/canvas/gitignore.ts
74046
- import { appendFile, readFile as readFile27 } from "fs/promises";
74278
+ import { appendFile, readFile as readFile28 } from "fs/promises";
74047
74279
  import path34 from "path";
74048
74280
  function missingGitignoreEntries(existing, entries) {
74049
74281
  const present2 = new Set(
@@ -74055,7 +74287,7 @@ async function ensureGitignore(dir, entries) {
74055
74287
  const file = path34.join(dir, ".gitignore");
74056
74288
  let existing;
74057
74289
  try {
74058
- existing = await readFile27(file, "utf8");
74290
+ existing = await readFile28(file, "utf8");
74059
74291
  } catch {
74060
74292
  return;
74061
74293
  }
@@ -74477,7 +74709,7 @@ var scaffoldVideoCommand = defineCommand136({
74477
74709
  await stampCompositionDims(compositionDest, outDims);
74478
74710
  const indexPath = path35.join(compositionDest, "index.html");
74479
74711
  const overlayHtml = buildOverlayHtml(blueprint, { captionsActive: Boolean(transcript), videoModel });
74480
- const indexHtml = await readFile28(indexPath, "utf8");
74712
+ const indexHtml = await readFile29(indexPath, "utf8");
74481
74713
  const injected = indexHtml.replace("<!--OVERLAYS-->", () => overlayHtml);
74482
74714
  if (injected === indexHtml && overlayHtml.trim()) {
74483
74715
  fail4(
@@ -74642,7 +74874,7 @@ var scaffoldVideoCommand = defineCommand136({
74642
74874
  });
74643
74875
 
74644
74876
  // src/commands/canvas/set-prompt.ts
74645
- import { readFile as readFile29, writeFile as writeFile16 } from "fs/promises";
74877
+ import { readFile as readFile30, writeFile as writeFile16 } from "fs/promises";
74646
74878
  import path36 from "path";
74647
74879
  import { defineCommand as defineCommand137 } from "citty";
74648
74880
  function setNodePrompt(canvas, nodeId, text2) {
@@ -74673,7 +74905,7 @@ var SetPromptError = class extends Error {
74673
74905
  };
74674
74906
  async function setPromptInCanvasFile(canvasPath, nodeId, text2) {
74675
74907
  const filePath = path36.resolve(canvasPath);
74676
- const original = await readFile29(filePath, "utf8");
74908
+ const original = await readFile30(filePath, "utf8");
74677
74909
  let canvas;
74678
74910
  try {
74679
74911
  canvas = JSON.parse(original);
@@ -74740,7 +74972,7 @@ var setPromptCommand = defineCommand137({
74740
74972
  },
74741
74973
  async run({ args: args8 }) {
74742
74974
  let text2;
74743
- if (args8["text-file"]) text2 = await readFile29(path36.resolve(String(args8["text-file"])), "utf8");
74975
+ if (args8["text-file"]) text2 = await readFile30(path36.resolve(String(args8["text-file"])), "utf8");
74744
74976
  else if (args8.text !== void 0) text2 = String(args8.text);
74745
74977
  else {
74746
74978
  process.stderr.write(
@@ -75497,7 +75729,7 @@ import { Buffer as Buffer2 } from "buffer";
75497
75729
  import { defineCommand as defineCommand146 } from "citty";
75498
75730
 
75499
75731
  // src/commands/images/api.ts
75500
- import { readFile as readFile30 } from "fs/promises";
75732
+ import { readFile as readFile31 } from "fs/promises";
75501
75733
  import { basename, extname } from "path";
75502
75734
  var imageProcessingTimeoutMs = 18e4;
75503
75735
  var imageReadyPollIntervalMs = 2e3;
@@ -75511,7 +75743,7 @@ var mimeMap = {
75511
75743
  ".avif": "image/avif"
75512
75744
  };
75513
75745
  var defaultImageApiDeps = {
75514
- readFile: readFile30,
75746
+ readFile: readFile31,
75515
75747
  post: apiPost,
75516
75748
  get: apiGet,
75517
75749
  sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms))
@@ -75577,7 +75809,7 @@ async function waitForReadyImage(deps, imageId, opts = {}) {
75577
75809
  }
75578
75810
 
75579
75811
  // src/commands/videos/api.ts
75580
- import { readFile as readFile31 } from "fs/promises";
75812
+ import { readFile as readFile32 } from "fs/promises";
75581
75813
  import { basename as basename2, extname as extname2 } from "path";
75582
75814
  var videoProcessingTimeoutMs = 18e4;
75583
75815
  var videoReadyPollIntervalMs = 3e3;
@@ -75589,7 +75821,7 @@ var mimeMap2 = {
75589
75821
  ".mkv": "video/x-matroska"
75590
75822
  };
75591
75823
  var defaultVideoApiDeps = {
75592
- readFile: readFile31,
75824
+ readFile: readFile32,
75593
75825
  post: apiPost,
75594
75826
  get: apiGet,
75595
75827
  sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms)),
@@ -75980,11 +76212,11 @@ Full guide: __tooling__/docs/tools/baker/creatives.md`
75980
76212
  import { defineCommand as defineCommand149 } from "citty";
75981
76213
 
75982
76214
  // src/commands/experiment/composition.ts
75983
- import { readdir as readdir14, readFile as readFile33 } from "fs/promises";
76215
+ import { readdir as readdir14, readFile as readFile34 } from "fs/promises";
75984
76216
  import path40 from "path";
75985
76217
 
75986
76218
  // src/commands/landing/import-graph.ts
75987
- import { readdir as readdir13, readFile as readFile32, stat as stat6 } from "fs/promises";
76219
+ import { readdir as readdir13, readFile as readFile33, stat as stat6 } from "fs/promises";
75988
76220
  import path39 from "path";
75989
76221
  var SOURCE_EXT = /\.(astro|ts|tsx|js|jsx|mjs|cjs|vue|svelte)$/;
75990
76222
  var RESOLVE_CANDIDATES = [
@@ -76061,7 +76293,7 @@ async function landingGraphFiles(root, slug) {
76061
76293
  if (file === void 0 || seen.has(file)) continue;
76062
76294
  let text2;
76063
76295
  try {
76064
- text2 = await readFile32(file, "utf8");
76296
+ text2 = await readFile33(file, "utf8");
76065
76297
  } catch {
76066
76298
  continue;
76067
76299
  }
@@ -76126,7 +76358,7 @@ async function readForms(root, slug) {
76126
76358
  const [files, flows] = await Promise.all([landingGraphFiles(root, slug), knownFlows(root)]);
76127
76359
  if (flows.size === 0) return [];
76128
76360
  const texts = await Promise.all(
76129
- [...files].filter((file) => /\.(astro|ts|tsx|js|mjs)$/.test(file)).map((file) => readFile33(path40.join(root, file), "utf8"))
76361
+ [...files].filter((file) => /\.(astro|ts|tsx|js|mjs)$/.test(file)).map((file) => readFile34(path40.join(root, file), "utf8"))
76130
76362
  );
76131
76363
  return formsReferenced(texts, flows);
76132
76364
  } catch {
@@ -76136,7 +76368,7 @@ async function readForms(root, slug) {
76136
76368
 
76137
76369
  // src/commands/experiment/fold.ts
76138
76370
  import { statSync } from "fs";
76139
- import { cp as cp3, mkdir as mkdir11, readdir as readdir15, readFile as readFile34, rm as rm9, stat as stat7, writeFile as writeFile17 } from "fs/promises";
76371
+ import { cp as cp3, mkdir as mkdir11, readdir as readdir15, readFile as readFile35, rm as rm9, stat as stat7, writeFile as writeFile17 } from "fs/promises";
76140
76372
  import path42 from "path";
76141
76373
 
76142
76374
  // ../api/src/experiments/variants.ts
@@ -76239,7 +76471,7 @@ async function archiveArm(opts) {
76239
76471
  if (!TEXT_EXT.test(rel)) {
76240
76472
  await cp3(abs, dest);
76241
76473
  } else {
76242
- const text2 = await readFile34(abs, "utf8");
76474
+ const text2 = await readFile35(abs, "utf8");
76243
76475
  await writeFile17(dest, repointIntoArchive(text2, abs, dest, archivedBySource), "utf8");
76244
76476
  }
76245
76477
  written.push(posix2(path42.relative(root, dest)));
@@ -76310,7 +76542,7 @@ async function promoteVariantFiles(opts) {
76310
76542
  if (!TEXT_EXT.test(rel)) {
76311
76543
  await cp3(from, to);
76312
76544
  } else {
76313
- const text2 = await readFile34(from, "utf8");
76545
+ const text2 = await readFile35(from, "utf8");
76314
76546
  await writeFile17(
76315
76547
  to,
76316
76548
  repointMoved(text2, {
@@ -77152,11 +77384,11 @@ function parseEvidence(raw, rawArgs) {
77152
77384
  }
77153
77385
 
77154
77386
  // src/commands/experiment/identity.ts
77155
- import { readFile as readFile35 } from "fs/promises";
77387
+ import { readFile as readFile36 } from "fs/promises";
77156
77388
  import path43 from "path";
77157
77389
  async function readPageInternalId(root, slug) {
77158
77390
  try {
77159
- const text2 = await readFile35(path43.resolve(root, "src", "pages", slug, "_definition.md"), "utf8");
77391
+ const text2 = await readFile36(path43.resolve(root, "src", "pages", slug, "_definition.md"), "utf8");
77160
77392
  return /^internalId:\s*"?([^"\s]+)"?\s*$/m.exec(text2)?.[1];
77161
77393
  } catch {
77162
77394
  return void 0;
@@ -77164,7 +77396,7 @@ async function readPageInternalId(root, slug) {
77164
77396
  }
77165
77397
 
77166
77398
  // src/commands/experiment/measurement.ts
77167
- import { readFile as readFile36 } from "fs/promises";
77399
+ import { readFile as readFile37 } from "fs/promises";
77168
77400
  import path44 from "path";
77169
77401
  function eventDeclarationsIn(source) {
77170
77402
  const found = [];
@@ -77225,7 +77457,7 @@ function sectionsOf(files, slug) {
77225
77457
  return files.filter((file) => file.startsWith(prefix) && !file.slice(prefix.length).startsWith("index."));
77226
77458
  }
77227
77459
  async function declarationsOf(root, files) {
77228
- const sources = await Promise.all(files.map((file) => readFile36(path44.join(root, file), "utf8").catch(() => "")));
77460
+ const sources = await Promise.all(files.map((file) => readFile37(path44.join(root, file), "utf8").catch(() => "")));
77229
77461
  return sources.flatMap(eventDeclarationsIn);
77230
77462
  }
77231
77463
 
@@ -83105,7 +83337,7 @@ function cropSprite(input, region) {
83105
83337
 
83106
83338
  // src/lib/image/io.ts
83107
83339
  import { randomBytes } from "crypto";
83108
- import { glob as fsGlob, readFile as readFile37, rename, stat as stat8, writeFile as writeFile18 } from "fs/promises";
83340
+ import { glob as fsGlob, readFile as readFile38, rename, stat as stat8, writeFile as writeFile18 } from "fs/promises";
83109
83341
  import { dirname as dirname2, extname as extname3, join as join5, resolve as resolve4 } from "path";
83110
83342
  var REMOTE_RE = /^https?:\/\//i;
83111
83343
  var GLOB_RE = /[*?[\]{}]/;
@@ -83138,7 +83370,7 @@ async function readImageBuffer(pathOrUrl) {
83138
83370
  const { buffer } = await fetchExternalBytes(pathOrUrl, { maxBytes: MAX_REMOTE_IMAGE_BYTES });
83139
83371
  return buffer;
83140
83372
  }
83141
- return readFile37(pathOrUrl);
83373
+ return readFile38(pathOrUrl);
83142
83374
  }
83143
83375
  async function isDirectory(path59) {
83144
83376
  try {
@@ -86687,7 +86919,7 @@ Full guide: __tooling__/docs/tools/baker/images.md`
86687
86919
  import { defineCommand as defineCommand207 } from "citty";
86688
86920
 
86689
86921
  // src/commands/landing/critique.ts
86690
- import { readdir as readdir17, readFile as readFile39, stat as stat10 } from "fs/promises";
86922
+ import { readdir as readdir17, readFile as readFile40, stat as stat10 } from "fs/promises";
86691
86923
  import path47 from "path";
86692
86924
  import { defineCommand as defineCommand196 } from "citty";
86693
86925
 
@@ -87898,7 +88130,7 @@ async function writeCritiqueSnapshot(projectRoot, snapshot) {
87898
88130
  }
87899
88131
 
87900
88132
  // src/commands/landing/source-version.ts
87901
- import { readdir as readdir16, readFile as readFile38, stat as stat9 } from "fs/promises";
88133
+ import { readdir as readdir16, readFile as readFile39, stat as stat9 } from "fs/promises";
87902
88134
  import path46 from "path";
87903
88135
  var CRITIQUED_ROOTS = ["src/pages/", "src/components/"];
87904
88136
  async function landingSourceRelPaths(root, slug) {
@@ -87908,7 +88140,7 @@ async function landingSourceRelPaths(root, slug) {
87908
88140
  async function readLandingSources(root, slug) {
87909
88141
  const rel = await landingSourceRelPaths(root, slug);
87910
88142
  const out = [];
87911
- for (const r of rel) out.push({ path: r, text: await readFile38(path46.join(root, r), "utf8") });
88143
+ for (const r of rel) out.push({ path: r, text: await readFile39(path46.join(root, r), "utf8") });
87912
88144
  return out;
87913
88145
  }
87914
88146
  async function computeLandingSourceSha(root, slug) {
@@ -87917,7 +88149,7 @@ async function computeLandingSourceSha(root, slug) {
87917
88149
  for (const r of rel) {
87918
88150
  let bytes;
87919
88151
  try {
87920
- bytes = await readFile38(path46.join(root, r));
88152
+ bytes = await readFile39(path46.join(root, r));
87921
88153
  } catch {
87922
88154
  bytes = Buffer.alloc(0);
87923
88155
  }
@@ -87936,7 +88168,7 @@ async function computeLegacyLandingSourceSha(landingDir) {
87936
88168
  for (const r of rel) {
87937
88169
  let bytes;
87938
88170
  try {
87939
- bytes = await readFile38(path46.join(landingDir, r));
88171
+ bytes = await readFile39(path46.join(landingDir, r));
87940
88172
  } catch {
87941
88173
  bytes = Buffer.alloc(0);
87942
88174
  }
@@ -88095,7 +88327,7 @@ async function readCompetitorNames(projectRoot) {
88095
88327
  for (const entry of entries) {
88096
88328
  names.push(path47.basename(entry, ".md").replace(/[-_]+/g, " "));
88097
88329
  try {
88098
- const head = (await readFile39(path47.join(dir, entry), "utf8")).slice(0, 2e3);
88330
+ const head = (await readFile40(path47.join(dir, entry), "utf8")).slice(0, 2e3);
88099
88331
  const titled = /^\s*(?:title|name)\s*:\s*["']?([^"'\n]+)["']?\s*$/im.exec(head);
88100
88332
  if (titled?.[1]) names.push(titled[1].trim());
88101
88333
  } catch {
@@ -91298,7 +91530,7 @@ Full guide: __tooling__/docs/tools/baker/landing.md`
91298
91530
 
91299
91531
  // src/commands/landing/variant.ts
91300
91532
  import { randomUUID as randomUUID3 } from "crypto";
91301
- import { mkdir as mkdir16, readdir as readdir18, readFile as readFile40, stat as stat11, writeFile as writeFile24 } from "fs/promises";
91533
+ import { mkdir as mkdir16, readdir as readdir18, readFile as readFile41, stat as stat11, writeFile as writeFile24 } from "fs/promises";
91302
91534
  import path55 from "path";
91303
91535
  import { defineCommand as defineCommand206 } from "citty";
91304
91536
  registerSchema({
@@ -91326,7 +91558,7 @@ registerSchema({
91326
91558
  var SLUG_RE2 = /^[a-z0-9][a-z0-9-]*$/;
91327
91559
  async function readInternalId(definitionPath) {
91328
91560
  try {
91329
- const text2 = await readFile40(definitionPath, "utf8");
91561
+ const text2 = await readFile41(definitionPath, "utf8");
91330
91562
  return /^internalId:\s*"?([^"\s]+)"?\s*$/m.exec(text2)?.[1] ?? null;
91331
91563
  } catch {
91332
91564
  return null;
@@ -91485,7 +91717,7 @@ async function writeVariant(opts) {
91485
91717
  const { controlDir, variantDir, controlSlug, variantSlug: variantSlug2, forks } = opts;
91486
91718
  const forkedTargets = new Set(forks.map((f) => path55.join(controlDir, "_components", f)));
91487
91719
  const written = [];
91488
- const indexText = await readFile40(path55.join(controlDir, "index.astro"), "utf8");
91720
+ const indexText = await readFile41(path55.join(controlDir, "index.astro"), "utf8");
91489
91721
  await mkdir16(variantDir, { recursive: true });
91490
91722
  await writeFile24(
91491
91723
  path55.join(variantDir, "index.astro"),
@@ -91496,7 +91728,7 @@ async function writeVariant(opts) {
91496
91728
  for (const fork of forks) {
91497
91729
  const fromFile = path55.join(controlDir, "_components", fork);
91498
91730
  const toFile = path55.join(variantDir, "_components", fork);
91499
- const text2 = await readFile40(fromFile, "utf8");
91731
+ const text2 = await readFile41(fromFile, "utf8");
91500
91732
  await mkdir16(path55.dirname(toFile), { recursive: true });
91501
91733
  await writeFile24(
91502
91734
  toFile,
@@ -91513,7 +91745,7 @@ async function writeVariant(opts) {
91513
91745
  }
91514
91746
  const controlDefinitionPath = path55.join(controlDir, "_definition.md");
91515
91747
  if (await exists2(controlDefinitionPath)) {
91516
- const definition = buildVariantDefinition(await readFile40(controlDefinitionPath, "utf8"), {
91748
+ const definition = buildVariantDefinition(await readFile41(controlDefinitionPath, "utf8"), {
91517
91749
  internalId: randomUUID3().replace(/-/g, "").slice(0, 8),
91518
91750
  variantSlug: variantSlug2,
91519
91751
  controlSlug,
@@ -94230,7 +94462,7 @@ Full guide: __tooling__/docs/tools/baker/scheduled-agents.md`
94230
94462
  });
94231
94463
 
94232
94464
  // src/commands/scheduled-agents/templates.ts
94233
- import { readFile as readFile41 } from "fs/promises";
94465
+ import { readFile as readFile42 } from "fs/promises";
94234
94466
  import path56 from "path";
94235
94467
  import { defineCommand as defineCommand234 } from "citty";
94236
94468
  registerSchema({
@@ -94338,7 +94570,7 @@ Full guide: __tooling__/docs/tools/baker/scheduled-agents.md`
94338
94570
  }
94339
94571
  if (save.length > 0) {
94340
94572
  const briefFile = flag("brief-file");
94341
- const brief2 = briefFile.length > 0 ? await readFile41(path56.resolve(briefFile), "utf8") : flag("brief");
94573
+ const brief2 = briefFile.length > 0 ? await readFile42(path56.resolve(briefFile), "utf8") : flag("brief");
94342
94574
  if (brief2.trim().length === 0) {
94343
94575
  failValidation4("--brief-file (preferred) or --brief is required: the brief is the recipe.");
94344
94576
  }
@@ -98068,7 +98300,7 @@ Full guide: __tooling__/docs/tools/baker/videos.md`
98068
98300
  });
98069
98301
 
98070
98302
  // src/commands/winning-ads/index.ts
98071
- import { defineCommand as defineCommand279 } from "citty";
98303
+ import { defineCommand as defineCommand277 } from "citty";
98072
98304
 
98073
98305
  // src/commands/winning-ads/advertisers.ts
98074
98306
  import { defineCommand as defineCommand267 } from "citty";
@@ -98181,90 +98413,8 @@ var advertisersCommand2 = defineCommand267({
98181
98413
  }
98182
98414
  });
98183
98415
 
98184
- // src/commands/winning-ads/brief.ts
98185
- import { defineCommand as defineCommand268 } from "citty";
98186
- registerSchema({
98187
- command: "winning-ads.brief",
98188
- description: "Generate a creative brief grounded in strategically-similar winning ads. Optionally describe the target creative with --dna (JSON) and steer with --notes.",
98189
- args: {
98190
- dna: {
98191
- type: "string",
98192
- description: `Partial target DNA as JSON, e.g. '{"angle":"cost savings","awareness_stage":"solution_aware"}'`,
98193
- required: false
98194
- },
98195
- notes: { type: "string", description: "Free-text guidance for the brief", required: false },
98196
- k: {
98197
- type: "number",
98198
- description: "Number of reference ads to pull 1-50 (default: service default)",
98199
- required: false
98200
- }
98201
- }
98202
- });
98203
- function buildBriefBody(args8) {
98204
- const body = {};
98205
- if (args8.targetDna && Object.keys(args8.targetDna).length) {
98206
- body.target_dna = args8.targetDna;
98207
- }
98208
- if (args8.notes) {
98209
- body.notes = args8.notes;
98210
- }
98211
- if (args8.k !== void 0 && args8.k !== "") {
98212
- body.k_reference_ads = Number(args8.k);
98213
- }
98214
- return body;
98215
- }
98216
- function parseDna(raw) {
98217
- if (!raw) {
98218
- return void 0;
98219
- }
98220
- let parsed;
98221
- try {
98222
- parsed = JSON.parse(raw);
98223
- } catch {
98224
- throw new SyntaxError("--dna must be valid JSON");
98225
- }
98226
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
98227
- throw new SyntaxError("--dna must be a JSON object");
98228
- }
98229
- return parsed;
98230
- }
98231
- var briefCommand = defineCommand268({
98232
- meta: {
98233
- name: "brief",
98234
- description: `Generate a creative brief from winning references. Example: baker winning-ads brief --dna '{"angle":"cost savings"}' --notes "B2B, LinkedIn video" --k 8`
98235
- },
98236
- args: {
98237
- dna: { type: "string", description: "Partial target DNA as JSON", required: false },
98238
- notes: { type: "string", description: "Free-text guidance", required: false },
98239
- k: { type: "string", description: "Number of reference ads (1-50)", required: false }
98240
- },
98241
- run: async ({ args: args8 }) => {
98242
- try {
98243
- let targetDna;
98244
- try {
98245
- targetDna = parseDna(args8.dna);
98246
- } catch (err) {
98247
- writeJson({
98248
- ok: false,
98249
- error: { code: "VALIDATION_ERROR", message: err instanceof Error ? err.message : "Invalid --dna" }
98250
- });
98251
- process.exit(1);
98252
- }
98253
- const body = buildBriefBody({
98254
- targetDna,
98255
- notes: args8.notes,
98256
- k: args8.k
98257
- });
98258
- const data = await apiPost("/api/ad-library/brief", body);
98259
- writeJson({ ok: true, data });
98260
- } catch (err) {
98261
- reportError3(err);
98262
- }
98263
- }
98264
- });
98265
-
98266
98416
  // src/commands/winning-ads/content.ts
98267
- import { defineCommand as defineCommand269 } from "citty";
98417
+ import { defineCommand as defineCommand268 } from "citty";
98268
98418
  registerSchema({
98269
98419
  command: "winning-ads.content",
98270
98420
  description: "Read what's INSIDE one winning ad: the spoken transcript, the on-screen text, and the ad copy. Use this after `search`/`winners`/`feed` return a shortlist \u2014 pass an ad_id to understand a reference before reproducing it. Static and carousel ads return the text drawn into the image with the job each piece does (headline, benefit, cta\u2026), the card it sits on, and their format_pattern. Add --full for how it is built (composition, colours, CTA, markers), how it persuades (awareness, mechanism, pains, desires, proof, objections), what it offers (offer type, price, guarantee, urgency), plus speech, pacing and soundtrack for video.",
@@ -98277,7 +98427,7 @@ registerSchema({
98277
98427
  }
98278
98428
  }
98279
98429
  });
98280
- var contentCommand = defineCommand269({
98430
+ var contentCommand = defineCommand268({
98281
98431
  meta: {
98282
98432
  name: "content",
98283
98433
  description: "Read the transcript + on-screen text + copy of one winning ad; --full adds its build, persuasion and offer. Example: baker winning-ads content adg_123 --platform meta --full --output md"
@@ -98326,7 +98476,7 @@ var contentCommand = defineCommand269({
98326
98476
  });
98327
98477
 
98328
98478
  // src/commands/winning-ads/feed.ts
98329
- import { defineCommand as defineCommand270 } from "citty";
98479
+ import { defineCommand as defineCommand269 } from "citty";
98330
98480
  function buildFeedParams(input) {
98331
98481
  const params = {};
98332
98482
  const advertiser = splitList4(input.advertiser);
@@ -98378,7 +98528,7 @@ registerSchema({
98378
98528
  format: { type: "string", description: "Comma-separated formats to include (e.g. static,video)", required: false }
98379
98529
  }
98380
98530
  });
98381
- var feedCommand2 = defineCommand270({
98531
+ var feedCommand2 = defineCommand269({
98382
98532
  meta: {
98383
98533
  name: "feed",
98384
98534
  description: "Winners across every brand you follow (browse, then trim per advertiser). Example: baker winning-ads feed --per-advertiser 5 --output md"
@@ -98463,7 +98613,7 @@ var feedCommand2 = defineCommand270({
98463
98613
  });
98464
98614
 
98465
98615
  // src/commands/winning-ads/follow.ts
98466
- import { defineCommand as defineCommand271 } from "citty";
98616
+ import { defineCommand as defineCommand270 } from "citty";
98467
98617
  var PLATFORMS = adLibraryPlatformSchema.options;
98468
98618
  registerSchema({
98469
98619
  command: "winning-ads.follow",
@@ -98478,7 +98628,7 @@ registerSchema({
98478
98628
  label: { type: "string", description: "Optional display label (defaults to the resolved name)", required: false }
98479
98629
  }
98480
98630
  });
98481
- var followCommand = defineCommand271({
98631
+ var followCommand = defineCommand270({
98482
98632
  meta: {
98483
98633
  name: "follow",
98484
98634
  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 every platform we can resolve. Example: baker winning-ads follow "deel.com" --platform meta'
@@ -98525,7 +98675,7 @@ var followCommand = defineCommand271({
98525
98675
  });
98526
98676
 
98527
98677
  // src/commands/winning-ads/follow-competitors.ts
98528
- import { defineCommand as defineCommand272 } from "citty";
98678
+ import { defineCommand as defineCommand271 } from "citty";
98529
98679
  var PLATFORMS2 = adLibraryPlatformSchema.options;
98530
98680
  var BATCH_TIMEOUT_MS = 3e5;
98531
98681
  function buildFollowBatchBody(input) {
@@ -98558,7 +98708,7 @@ registerSchema({
98558
98708
  }
98559
98709
  }
98560
98710
  });
98561
- var followCompetitorsCommand = defineCommand272({
98711
+ var followCompetitorsCommand = defineCommand271({
98562
98712
  meta: {
98563
98713
  name: "follow-competitors",
98564
98714
  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"'
@@ -98633,7 +98783,7 @@ var followCompetitorsCommand = defineCommand272({
98633
98783
  });
98634
98784
 
98635
98785
  // src/commands/winning-ads/following.ts
98636
- import { defineCommand as defineCommand273 } from "citty";
98786
+ import { defineCommand as defineCommand272 } from "citty";
98637
98787
  registerSchema({
98638
98788
  command: "winning-ads.following",
98639
98789
  description: "List the brands you follow in your ad-dna library, with each one's status (ready vs still adding) and cached ad counts. A brand still adding has counts that are a lie in progress; one with `discovery_failed` has counts that are short because we couldn't finish looking, which is not the same as it running no ads.",
@@ -98687,7 +98837,7 @@ function followingNormalizer(record, full) {
98687
98837
  platforms: Array.isArray(record.platforms) ? record.platforms : []
98688
98838
  };
98689
98839
  }
98690
- var followingCommand = defineCommand273({
98840
+ var followingCommand = defineCommand272({
98691
98841
  meta: {
98692
98842
  name: "following",
98693
98843
  description: "List brands you follow, with status (ready / adding\u2026) and cached counts. A brand's counts are only final once it is ready. Example: baker winning-ads following --output md"
@@ -98722,113 +98872,18 @@ var followingCommand = defineCommand273({
98722
98872
  }
98723
98873
  });
98724
98874
 
98725
- // src/commands/winning-ads/patterns.ts
98726
- import { defineCommand as defineCommand274 } from "citty";
98727
- registerSchema({
98728
- command: "winning-ads.patterns",
98729
- 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.",
98730
- args: {
98731
- winners: { type: "string", description: "Comma list of winning ad ids (cohort A)", required: true },
98732
- duds: { type: "string", description: "Comma list of weaker/dud ad ids (cohort B)", required: true },
98733
- "top-n": {
98734
- type: "number",
98735
- description: "Max discriminators to return 1-50 (default: service default)",
98736
- required: false
98737
- }
98738
- }
98739
- });
98740
- function buildPatternsBody(args8) {
98741
- const winners = splitList4(args8.winners);
98742
- const duds = splitList4(args8.duds);
98743
- if (!winners.length || !duds.length) {
98744
- throw new Error("Provide at least one ad id for both --winners and --duds");
98745
- }
98746
- const body = {
98747
- cohort_a: { ad_ids: winners },
98748
- cohort_b: { ad_ids: duds }
98749
- };
98750
- if (args8.topN !== void 0 && args8.topN !== "") {
98751
- body.top_n = Number(args8.topN);
98752
- }
98753
- return body;
98754
- }
98755
- function discriminatorRow(record) {
98756
- const dir = record.direction === "favors_a" ? "winners" : "duds";
98757
- return {
98758
- field: String(record.field ?? ""),
98759
- favors: dir,
98760
- score: record.score ?? null,
98761
- top_values_winners: Array.isArray(record.top_values_a) ? record.top_values_a.join(", ") : "",
98762
- top_values_duds: Array.isArray(record.top_values_b) ? record.top_values_b.join(", ") : ""
98763
- };
98764
- }
98765
- var patternsCommand = defineCommand274({
98766
- meta: {
98767
- name: "patterns",
98768
- 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"
98769
- },
98770
- args: {
98771
- winners: { type: "string", description: "Comma list of winning ad ids (cohort A)", required: true },
98772
- duds: { type: "string", description: "Comma list of weaker/dud ad ids (cohort B)", required: true },
98773
- "top-n": { type: "string", description: "Max discriminators (1-50)", required: false },
98774
- output: { type: "string", description: "Output format: json|files|md", required: false, default: "json" },
98775
- fields: { type: "string", description: "Comma-separated field names to include", required: false }
98776
- },
98777
- run: async ({ args: args8 }) => {
98778
- try {
98779
- let body;
98780
- try {
98781
- body = buildPatternsBody({
98782
- winners: args8.winners,
98783
- duds: args8.duds,
98784
- topN: args8["top-n"]
98785
- });
98786
- } catch (err) {
98787
- writeJson({
98788
- ok: false,
98789
- error: { code: "VALIDATION_ERROR", message: err instanceof Error ? err.message : "Invalid cohorts" }
98790
- });
98791
- process.exit(1);
98792
- }
98793
- const data = await apiPost("/api/ad-library/patterns", body);
98794
- const output = args8.output || "json";
98795
- const rawDiscriminators = Array.isArray(data?.discriminators) ? data.discriminators : [];
98796
- if (output === "json") {
98797
- writeJson({
98798
- ok: true,
98799
- data: {
98800
- cohort_a_size: data?.cohort_a_size ?? 0,
98801
- cohort_b_size: data?.cohort_b_size ?? 0,
98802
- discriminators: rawDiscriminators.map((d) => discriminatorRow(d))
98803
- }
98804
- });
98805
- return;
98806
- }
98807
- writeOutput(
98808
- { ok: true, data: rawDiscriminators },
98809
- output,
98810
- args8.fields ? args8.fields.split(",") : void 0,
98811
- false,
98812
- (record) => discriminatorRow(record)
98813
- );
98814
- } catch (err) {
98815
- reportError3(err);
98816
- }
98817
- }
98818
- });
98819
-
98820
98875
  // src/commands/winning-ads/search.ts
98821
- import { defineCommand as defineCommand275 } from "citty";
98876
+ import { defineCommand as defineCommand273 } from "citty";
98822
98877
  registerSchema({
98823
98878
  command: "winning-ads.search",
98824
- description: "Search the ad-dna corpus of scored winning ads. Returns a lean shortlist (advertiser, summary, scores, media_url, and visual/container/mechanism when known) to pick a reference to reproduce. A value outside a closed list (--platform, --format, --winner-category, --awareness, --format-pattern) is refused with error.fix.validValues.",
98879
+ description: "Search the ad-dna corpus of scored winning ads, the company's followed brands first. Returns a lean shortlist (advertiser, summary, scores, days_active/active/winner_category as proof, media_url, and visual/container/mechanism when known) to pick a reference to reproduce. Each search is a paid reranker + judge call: one strong search, at most one adjusted retry. A value outside a closed list (--platform, --format, --winner-category, --awareness, --format-pattern, --sort) is refused with error.fix.validValues.",
98825
98880
  args: {
98826
98881
  query: { type: "string", description: "Free-text natural-language query", required: false },
98827
98882
  scope: {
98828
98883
  type: "string",
98829
- description: "Which brands to draw from: all (default, the whole shared library), followed (only brands the company follows), followed-first (followed brands first, topped up from the library when too few clear the relevance floor). Every result carries source: followed|library.",
98884
+ description: "Which brands to draw from: followed-first (default: followed brands first, topped up from the library when too few clear the relevance floor; the whole library when the company follows nobody), followed (only brands the company follows), all (the whole shared library). Every result carries source: followed|library.",
98830
98885
  required: false,
98831
- default: "all"
98886
+ default: "followed-first"
98832
98887
  },
98833
98888
  "ref-ad-id": {
98834
98889
  type: "string",
@@ -98843,6 +98898,12 @@ registerSchema({
98843
98898
  default: 3
98844
98899
  },
98845
98900
  "min-relevance": { type: "number", description: "Relevance floor 0-1; trims weak matches", required: false },
98901
+ sort: {
98902
+ type: "string",
98903
+ description: "Order of a browse with no query: winner (default, by winner score), longest_running (by days active), newest (by first seen). Ignored when a query is given: a query ranks by relevance blended with winner score.",
98904
+ required: false,
98905
+ default: "winner"
98906
+ },
98846
98907
  platform: {
98847
98908
  type: "string",
98848
98909
  description: "Comma list: meta,linkedin,tiktok",
@@ -98931,6 +98992,7 @@ function buildSearchBody2(args8) {
98931
98992
  setNumber(body, "limit", args8.limit);
98932
98993
  setNumber(body, "max_per_advertiser", args8.maxPerAdvertiser);
98933
98994
  setNumber(body, "min_relevance", args8.minRelevance);
98995
+ setString(body, "sort", args8.sort);
98934
98996
  if (args8.diverse) {
98935
98997
  body.diverse = true;
98936
98998
  }
@@ -99051,6 +99113,9 @@ function searchRejection(args8, body) {
99051
99113
  if (args8.scope !== void 0 && !winnerSearchScopeSchema.safeParse(args8.scope).success) {
99052
99114
  return { message: `Unknown --scope "${args8.scope}". Use one of: ${winnerSearchScopeSchema.options.join(", ")}.` };
99053
99115
  }
99116
+ if (args8.sort !== void 0 && !winnersSortSchema.safeParse(args8.sort).success) {
99117
+ return { message: `Unknown --sort "${args8.sort}". Use one of: ${winnersSortSchema.options.join(", ")}.` };
99118
+ }
99054
99119
  for (const [flag, key] of NUMERIC_FLAGS) {
99055
99120
  const value2 = args8[key];
99056
99121
  if (value2 !== void 0 && value2 !== "" && Number.isNaN(Number(value2))) {
@@ -99086,16 +99151,16 @@ function writeSearchResult(data, opts) {
99086
99151
  writeOutput({ ok: true, data: rawResults, ...withHints2 }, opts.output, opts.fields, opts.full, winningAdNormalizer);
99087
99152
  }
99088
99153
  var SEARCH_TIMEOUT_MS = 12e4;
99089
- var searchCommand5 = defineCommand275({
99154
+ var searchCommand5 = defineCommand273({
99090
99155
  meta: {
99091
99156
  name: "search",
99092
- description: "Search winning reference ads. Start here: baker winning-ads search 'B2B SaaS before/after AI automation' --scope followed-first --platform meta --format static --exclude-advertiser adv_123 --output md. A set of references for N concepts in one call: add --diverse --limit N. The best proven ad in one container from any category: --format-pattern comparison_table (no query needed)."
99157
+ description: "Search winning reference ads, the company's followed brands first. Start here: baker winning-ads search 'B2B SaaS before/after AI automation' --platform meta --format static --exclude-advertiser adv_123 --output md. A set of references for N concepts in one call: add --diverse --limit N. The best proven ad in one container from any category: --format-pattern comparison_table (no query needed; add --sort longest_running to order by staying power). Each search is a paid reranker + judge call: one strong search, at most one adjusted retry."
99093
99158
  },
99094
99159
  args: {
99095
99160
  query: { type: "positional", description: "Free-text search query", required: false },
99096
99161
  scope: {
99097
99162
  type: "string",
99098
- description: "all (default) | followed | followed-first. followed-first = the company's followed brands first, topped up from the whole library",
99163
+ description: "followed-first (default) | followed | all. followed-first = the company's followed brands first, topped up from the whole library (the whole library when it follows nobody)",
99099
99164
  required: false
99100
99165
  },
99101
99166
  "ref-ad-id": { type: "string", description: "Find ads similar to this ad id", required: false },
@@ -99111,6 +99176,11 @@ var searchCommand5 = defineCommand275({
99111
99176
  required: false
99112
99177
  },
99113
99178
  "min-relevance": { type: "string", description: "Relevance floor 0-1", required: false },
99179
+ sort: {
99180
+ type: "string",
99181
+ description: "Order of a no-query browse: winner (default) | longest_running | newest. Ignored when a query is given",
99182
+ required: false
99183
+ },
99114
99184
  platform: {
99115
99185
  type: "string",
99116
99186
  description: "Comma list of platforms (meta,linkedin,tiktok) \u2014 search one or several",
@@ -99157,7 +99227,7 @@ var searchCommand5 = defineCommand275({
99157
99227
  fields: { type: "string", description: "Comma-separated field names to include", required: false },
99158
99228
  full: {
99159
99229
  type: "boolean",
99160
- description: "Include DNA detail (angle, persona, hook) + longevity; mechanism is in the default output",
99230
+ description: "Include DNA detail (angle, persona, hook, awareness, industry) + reach and media kind; mechanism, days_active, active and winner_category are in the default output",
99161
99231
  required: false,
99162
99232
  default: false
99163
99233
  }
@@ -99171,6 +99241,7 @@ var searchCommand5 = defineCommand275({
99171
99241
  limit: args8.limit,
99172
99242
  maxPerAdvertiser: args8["max-per-advertiser"],
99173
99243
  minRelevance: args8["min-relevance"],
99244
+ sort: args8.sort,
99174
99245
  platform: args8.platform,
99175
99246
  format: args8.format,
99176
99247
  winnerCategory: args8["winner-category"],
@@ -99206,7 +99277,7 @@ var searchCommand5 = defineCommand275({
99206
99277
  });
99207
99278
 
99208
99279
  // src/commands/winning-ads/seeds.ts
99209
- import { defineCommand as defineCommand276 } from "citty";
99280
+ import { defineCommand as defineCommand274 } from "citty";
99210
99281
  function leanRow(r) {
99211
99282
  return {
99212
99283
  key: r.key,
@@ -99234,7 +99305,7 @@ function makeSeedCommand(opts) {
99234
99305
  limit: { type: "number", description: "Max keys 1-100 (default 20)", required: false, default: 20 }
99235
99306
  }
99236
99307
  });
99237
- return defineCommand276({
99308
+ return defineCommand274({
99238
99309
  meta: { name: opts.name, description: opts.description },
99239
99310
  args: {
99240
99311
  platform: { type: "string", description: "Single platform to segment on", required: false },
@@ -99283,7 +99354,7 @@ var formatsCommand = makeSeedCommand({
99283
99354
  });
99284
99355
 
99285
99356
  // src/commands/winning-ads/unfollow.ts
99286
- import { defineCommand as defineCommand277 } from "citty";
99357
+ import { defineCommand as defineCommand275 } from "citty";
99287
99358
  registerSchema({
99288
99359
  command: "winning-ads.unfollow",
99289
99360
  description: "Stop following a brand \u2014 removes it from your ad-dna library by advertiser id.",
@@ -99291,7 +99362,7 @@ registerSchema({
99291
99362
  advertiser: { type: "string", description: "Advertiser id to unfollow", required: true }
99292
99363
  }
99293
99364
  });
99294
- var unfollowCommand = defineCommand277({
99365
+ var unfollowCommand = defineCommand275({
99295
99366
  meta: {
99296
99367
  name: "unfollow",
99297
99368
  description: "Stop following a brand by advertiser id. Example: baker winning-ads unfollow adv_123"
@@ -99312,7 +99383,7 @@ var unfollowCommand = defineCommand277({
99312
99383
  });
99313
99384
 
99314
99385
  // src/commands/winning-ads/winners.ts
99315
- import { defineCommand as defineCommand278 } from "citty";
99386
+ import { defineCommand as defineCommand276 } from "citty";
99316
99387
  registerSchema({
99317
99388
  command: "winning-ads.winners",
99318
99389
  description: "Top winning ads for one advertiser id (from `advertisers` or `following`). Returns lean winner cards; add --full for DNA + longevity.",
@@ -99322,7 +99393,7 @@ registerSchema({
99322
99393
  platform: { type: "string", description: "Filter to a single platform: meta|linkedin|tiktok", required: false }
99323
99394
  }
99324
99395
  });
99325
- var winnersCommand = defineCommand278({
99396
+ var winnersCommand = defineCommand276({
99326
99397
  meta: {
99327
99398
  name: "winners",
99328
99399
  description: "Top winning ads for a specific advertiser id. Example: baker winning-ads winners adv_123 --top 15 --output md"
@@ -99372,7 +99443,7 @@ var winnersCommand = defineCommand278({
99372
99443
  });
99373
99444
 
99374
99445
  // src/commands/winning-ads/index.ts
99375
- var winningAdsCommand = defineCommand279({
99446
+ var winningAdsCommand = defineCommand277({
99376
99447
  meta: {
99377
99448
  name: "winning-ads",
99378
99449
  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.
@@ -99380,7 +99451,7 @@ var winningAdsCommand = defineCommand279({
99380
99451
  Auth: BAKER_API_KEY (bk_ or bkr_) + BAKER_API_URL (your Convex .convex.site URL).
99381
99452
 
99382
99453
  Subcommands:
99383
- baker winning-ads search "<free text>" \u2014 semantic search across the corpus; lean shortlist (advertiser, summary, scores, media_url)
99454
+ baker winning-ads search "<free text>" \u2014 semantic search, followed brands first; lean shortlist (advertiser, summary, scores, proof, media_url)
99384
99455
  baker winning-ads advertisers "<brand>" \u2014 list corpus brands \u2192 advertiser ids (for --exclude-advertiser / --advertiser-id)
99385
99456
  baker winning-ads follow "<domain|url|brand>" --platform meta|linkedin|tiktok \u2014 add a brand's ads to your library
99386
99457
  baker winning-ads follow-competitors "<domain,domain,...>" \u2014 add MANY brands at once (every competitor by domain)
@@ -99389,8 +99460,6 @@ Subcommands:
99389
99460
  baker winning-ads winners <advertiser> \u2014 top winners for one advertiser id
99390
99461
  baker winning-ads content <ad-id> \u2014 read ONE ad's transcript + on-screen text + copy (understand a reference before reproducing it)
99391
99462
  baker winning-ads unfollow <advertiser> \u2014 stop following a brand
99392
- baker winning-ads brief \u2014 creative brief grounded in similar winners
99393
- baker winning-ads patterns --winners \u2026 --duds \u2026 \u2014 what separates a winning cohort from a weak one
99394
99463
  baker winning-ads hooks \u2014 top winner-weighted HOOK seeds for a segment (feed the seed to search --hook-archetype)
99395
99464
  baker winning-ads mechanisms \u2014 top winner-weighted PRODUCT mechanism seeds (how the product claims to work)
99396
99465
  baker winning-ads formats \u2014 top winner-weighted static FORMAT patterns for a segment
@@ -99406,7 +99475,7 @@ Examples:
99406
99475
  baker winning-ads content adg_123 --full --output md
99407
99476
  baker winning-ads hooks --platform meta --awareness problem_aware --industry saas --output md
99408
99477
  baker winning-ads search "fintech onboarding" --hook-archetype callout --winner-category winner --output md
99409
- baker winning-ads patterns --winners a_1,a_2 --duds a_9,a_8 --output md
99478
+ baker winning-ads search --format-pattern comparison_table --sort longest_running --output md
99410
99479
  Full guide: __tooling__/docs/tools/baker/winning-ads.md`
99411
99480
  },
99412
99481
  subCommands: {
@@ -99419,8 +99488,6 @@ Full guide: __tooling__/docs/tools/baker/winning-ads.md`
99419
99488
  winners: winnersCommand,
99420
99489
  content: contentCommand,
99421
99490
  unfollow: unfollowCommand,
99422
- brief: briefCommand,
99423
- patterns: patternsCommand,
99424
99491
  hooks: hooksCommand,
99425
99492
  mechanisms: mechanismsCommand,
99426
99493
  formats: formatsCommand
@@ -99444,7 +99511,7 @@ function getCliVersion() {
99444
99511
  }
99445
99512
 
99446
99513
  // src/cli.ts
99447
- var main = defineCommand280({
99514
+ var main = defineCommand278({
99448
99515
  meta: {
99449
99516
  name: "baker",
99450
99517
  version: getCliVersion(),