@koda-sl/baker-cli 0.407.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,
@@ -26314,6 +26286,9 @@ var chatChangeTypeSchema = z13.enum([
26314
26286
  "conversions",
26315
26287
  // An A/B test between a landing and its alternative. Staged, not immediate.
26316
26288
  "experiment",
26289
+ // A company's own check (a rule the agent follows in their workspace).
26290
+ // Filesystem work, staged on the Session branch like a landing.
26291
+ "check",
26317
26292
  "briefs"
26318
26293
  ]);
26319
26294
  var chatChangeActionSchema = z13.enum(["created", "updated", "deleted"]);
@@ -35331,8 +35306,8 @@ var MEMBER_ARGS = {
35331
35306
  },
35332
35307
  country: { type: "string", description: "Country of phones written without a +code, e.g. ES" }
35333
35308
  };
35334
- function membersFromArgs(args8, readFile42 = (path59) => readFileSync2(path59, "utf8")) {
35335
- 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: [] };
35336
35311
  const given = [...csvArg(args8.emails) ?? [], ...fromFile.emails];
35337
35312
  const givenPhones = [...csvArg(args8.phones) ?? [], ...fromFile.phones];
35338
35313
  const members = hashedMembers({
@@ -53527,10 +53502,10 @@ Example: baker ads meta ads retag 120252158038400247 --url-tags "utm_source={{si
53527
53502
  }
53528
53503
  },
53529
53504
  run: async ({ args: args8 }) => {
53530
- const { apiPost: apiPost2 } = await import("./client-BPKOHQWA.js");
53531
- const { requireChatId: requireChatId2 } = await import("./env-B7V77K6K.js");
53532
- const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-5M2QPMKZ.js");
53533
- 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");
53534
53509
  try {
53535
53510
  const accountId = bareAccountId2(args8);
53536
53511
  const urlTags = typeof args8["url-tags"] === "string" ? args8["url-tags"] : void 0;
@@ -53681,10 +53656,10 @@ function duplicateCommand2(entity, label) {
53681
53656
  replace: { type: "boolean", description: "Pause the original once the copy publishes" }
53682
53657
  },
53683
53658
  run: async ({ args: args8 }) => {
53684
- const { apiPost: apiPost2 } = await import("./client-BPKOHQWA.js");
53685
- const { requireChatId: requireChatId2 } = await import("./env-B7V77K6K.js");
53686
- const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-5M2QPMKZ.js");
53687
- 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");
53688
53663
  try {
53689
53664
  const accountId = bareAccountId2(args8);
53690
53665
  const chatId = requireChatId2();
@@ -71571,7 +71546,7 @@ var rerunCommand = defineCommand132({
71571
71546
  });
71572
71547
 
71573
71548
  // src/commands/canvas/scaffold-static-ad.ts
71574
- 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";
71575
71550
  import path31 from "path";
71576
71551
  import { defineCommand as defineCommand134 } from "citty";
71577
71552
 
@@ -71912,6 +71887,7 @@ function fileExists(target2) {
71912
71887
 
71913
71888
  // src/commands/canvas/creative-definition.ts
71914
71889
  import path28 from "path";
71890
+ import { parseDocument as parseDocument2 } from "yaml";
71915
71891
  var PLATFORM_VALUES = ["meta", "google", "linkedin", "tiktok", "youtube", "x", "other"];
71916
71892
  var FORMAT_VALUES = ["1:1", "4:5", "9:16", "16:9", "1.91:1"];
71917
71893
  function titleFromSlug(slug) {
@@ -71950,6 +71926,9 @@ function buildCreativeDefinition(input) {
71950
71926
  lines.push(`status: ${input.status ?? "draft"}`);
71951
71927
  if (input.sourceReferenceUrl) lines.push(`sourceReferenceUrl: ${yamlScalar(input.sourceReferenceUrl)}`);
71952
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}`);
71953
71932
  if (input.sourceKind) lines.push(`sourceKind: ${input.sourceKind}`);
71954
71933
  if (input.sourcePath) lines.push(`sourcePath: ${yamlScalar(input.sourcePath)}`);
71955
71934
  lines.push("---", "");
@@ -71957,6 +71936,19 @@ function buildCreativeDefinition(input) {
71957
71936
  lines.push("");
71958
71937
  return lines.join("\n");
71959
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
+ }
71960
71952
 
71961
71953
  // src/commands/canvas/scaffold-static-ad-paths.ts
71962
71954
  import path29 from "path";
@@ -71978,7 +71970,7 @@ function isValidScaffoldSlug(slug) {
71978
71970
  }
71979
71971
 
71980
71972
  // src/commands/canvas/sync-definition.ts
71981
- import { readdir as readdir11, stat as stat4 } from "fs/promises";
71973
+ import { readdir as readdir11, readFile as readFile23, stat as stat4 } from "fs/promises";
71982
71974
  import path30 from "path";
71983
71975
  import { defineCommand as defineCommand133 } from "citty";
71984
71976
 
@@ -72026,6 +72018,21 @@ function canvasToDefinitionGraph(canvas) {
72026
72018
  }
72027
72019
 
72028
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
+ }
72029
72036
  async function syncCreativeDefinitionBestEffort(input) {
72030
72037
  const chatId = process.env.BAKER_CHAT_ID;
72031
72038
  if (!chatId) return;
@@ -72034,16 +72041,7 @@ async function syncCreativeDefinitionBestEffort(input) {
72034
72041
  try {
72035
72042
  const creds = requireCredentialsFromEnv();
72036
72043
  const client = new BackendClient2({ baseUrl: creds.url, apiKey: creds.apiKey });
72037
- await client.syncCreativeDefinition({
72038
- slug: input.slug,
72039
- title: input.title,
72040
- platform: input.platform,
72041
- formats: input.formats,
72042
- sourceReferenceUrl: input.sourceReferenceUrl,
72043
- graph,
72044
- sourceSha: input.sourceSha,
72045
- chatId
72046
- });
72044
+ await client.syncCreativeDefinition(buildDefinitionSyncPayload(input, graph, chatId));
72047
72045
  process.stdout.write(`[definition] synced workflow graph (${graph.nodes.length} nodes) \u2014 view it in the dashboard
72048
72046
  `);
72049
72047
  } catch (e) {
@@ -72102,7 +72100,7 @@ var syncDefinitionCommand = defineCommand133({
72102
72100
  } catch {
72103
72101
  return;
72104
72102
  }
72105
- await syncCreativeDefinitionBestEffort({ slug, canvas, sourceSha });
72103
+ await syncCreativeDefinitionBestEffort({ slug, canvas, sourceSha, ...await readDefinitionSourceFields(canvasPath) });
72106
72104
  }
72107
72105
  });
72108
72106
 
@@ -72135,6 +72133,117 @@ async function resolveCastAvatars(flag) {
72135
72133
  return { handles, avatars };
72136
72134
  }
72137
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
+
72138
72247
  // src/commands/canvas/scaffold-static-ad.ts
72139
72248
  function toCanvasRelative2(canvasDir, file) {
72140
72249
  const rel = path31.relative(canvasDir, file).split(path31.sep).join("/");
@@ -72149,7 +72258,7 @@ async function uploadSourceAsReference(source, isUrl, client) {
72149
72258
  throw new Error(`failed to download source image (${e instanceof Error ? e.message : String(e)})`);
72150
72259
  }
72151
72260
  } else {
72152
- bytes = await readFile23(source);
72261
+ bytes = await readFile24(source);
72153
72262
  }
72154
72263
  const safe = await toModelSafeImage(bytes);
72155
72264
  const sha2562 = sha256Hex(safe.bytes);
@@ -72289,13 +72398,102 @@ async function runVisionPasses(canvas) {
72289
72398
  return fail4("read_outputs", e instanceof Error ? e.message : String(e));
72290
72399
  }
72291
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
+ }
72292
72483
  var scaffoldStaticAdCommand = defineCommand134({
72293
72484
  meta: {
72294
72485
  name: "scaffold-static-ad",
72295
- 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."
72296
72487
  },
72297
72488
  args: {
72298
- 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 },
72299
72497
  context: { type: "string", description: "Known provenance (advertiser, category, market) to ground the describe" },
72300
72498
  out: { type: "string", description: "Output canvas path (default <image-dir>/static-ad.canvas.json)" },
72301
72499
  slug: {
@@ -72307,7 +72505,10 @@ var scaffoldStaticAdCommand = defineCommand134({
72307
72505
  type: "string",
72308
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)."
72309
72507
  },
72310
- 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
+ },
72311
72512
  "describe-model": { type: "string", description: "Override the image_describe model id" },
72312
72513
  "select-model": { type: "string", description: "Override the text_generate model id for element selection" },
72313
72514
  "gen-model": { type: "string", description: "Override the image_generate model id" },
@@ -72341,12 +72542,22 @@ var scaffoldStaticAdCommand = defineCommand134({
72341
72542
  );
72342
72543
  process.exit(2);
72343
72544
  }
72344
- const { imageIsUrl, imageSource, outPath, blueprintPath: blueprintPath2, definitionPath, referencesDir } = resolveScaffoldStaticAdPaths(
72345
- String(args8.file),
72346
- args8.out ? String(args8.out) : void 0,
72347
- process.cwd(),
72348
- slug
72349
- );
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);
72350
72561
  if (slug) await enforceNewCreativeLimit(slug, outPath);
72351
72562
  await mkdir8(path31.dirname(outPath), { recursive: true });
72352
72563
  let models;
@@ -72374,10 +72585,11 @@ var scaffoldStaticAdCommand = defineCommand134({
72374
72585
  durableSourceUrl !== void 0 || imageIsUrl,
72375
72586
  describeModel,
72376
72587
  selectModel,
72377
- args8.context ? String(args8.context) : void 0
72588
+ referenceDescribeContext(reference, args8.context ? String(args8.context) : void 0)
72378
72589
  );
72379
72590
  const vision = await runVisionPasses(describeCanvas);
72380
- const { blueprint, creditsSpent } = vision;
72591
+ const { creditsSpent } = vision;
72592
+ const blueprint = mergeReferenceIntoBlueprint(vision.blueprint, reference);
72381
72593
  const elements = castAvatarsOnStaticElements(vision.elements, cast.avatars);
72382
72594
  const uncast = staticAvatarsNotCast(vision.elements, cast.handles);
72383
72595
  const canvasDir = path31.dirname(outPath);
@@ -72438,6 +72650,15 @@ var scaffoldStaticAdCommand = defineCommand134({
72438
72650
  }
72439
72651
  await writeFile12(outPath, `${JSON.stringify(canvas, null, 2)}
72440
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
+ } : {};
72441
72662
  if (definitionPath && !await fileExists(definitionPath)) {
72442
72663
  await writeFile12(
72443
72664
  definitionPath,
@@ -72447,7 +72668,8 @@ var scaffoldStaticAdCommand = defineCommand134({
72447
72668
  platform: definitionPlatform,
72448
72669
  formats: resolveFormatList(report.output_formats),
72449
72670
  sourceReferenceUrl: durableSourceUrl ?? (imageIsUrl ? imageSource : void 0),
72450
- 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,
72451
72673
  sourceKind: "image",
72452
72674
  description: describeBlueprintIntent(blueprint)
72453
72675
  }),
@@ -72461,11 +72683,15 @@ var scaffoldStaticAdCommand = defineCommand134({
72461
72683
  platform: definitionPlatform,
72462
72684
  formats: resolveFormatList(report.output_formats),
72463
72685
  sourceReferenceUrl: durableSourceUrl ?? (imageIsUrl ? imageSource : void 0),
72686
+ ...sourceFields,
72464
72687
  canvas,
72465
72688
  sourceSha: await computeSourceSha(outPath)
72466
72689
  });
72467
72690
  }
72468
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
+ ] : [],
72469
72695
  ...uncast.length ? [
72470
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.`
72471
72697
  ] : [],
@@ -72482,6 +72708,15 @@ var scaffoldStaticAdCommand = defineCommand134({
72482
72708
  prompt_path: blueprintPath2,
72483
72709
  definition_path: definitionPath ?? void 0,
72484
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
+ } : {},
72485
72720
  output: canvas.output,
72486
72721
  ...hints2.length ? { hints: hints2 } : {},
72487
72722
  models: { describe: describeModel, select: selectModel, gen: opts.genModel },
@@ -72494,7 +72729,7 @@ var scaffoldStaticAdCommand = defineCommand134({
72494
72729
  run_estimated_credits: validation.estimatedCredits
72495
72730
  },
72496
72731
  checklist: {
72497
- 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." : ""}`,
72498
72733
  winning_mechanisms: report.winning_mechanisms,
72499
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.",
72500
72735
  assets_to_supply: report.elements,
@@ -72555,7 +72790,7 @@ function clientLogoMissNote(decision, domain) {
72555
72790
  }
72556
72791
 
72557
72792
  // src/commands/canvas/scaffold-ad.ts
72558
- 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";
72559
72794
  import path33 from "path";
72560
72795
  import { defineCommand as defineCommand135 } from "citty";
72561
72796
 
@@ -73270,7 +73505,7 @@ function injectAdBrandOverlay(indexHtml, overlayHtml, style = {}) {
73270
73505
  }
73271
73506
 
73272
73507
  // src/commands/canvas/composition-dims.ts
73273
- import { readFile as readFile24, writeFile as writeFile13 } from "fs/promises";
73508
+ import { readFile as readFile25, writeFile as writeFile13 } from "fs/promises";
73274
73509
  import path32 from "path";
73275
73510
  function patchCompositionMeta(metaJson, dims) {
73276
73511
  try {
@@ -73287,10 +73522,10 @@ function patchCompositionHtml(html, dims) {
73287
73522
  }
73288
73523
  async function stampCompositionDims(compositionDir, dims) {
73289
73524
  const metaPath = path32.join(compositionDir, "meta.json");
73290
- const rawMeta = await readFile24(metaPath, "utf8");
73525
+ const rawMeta = await readFile25(metaPath, "utf8");
73291
73526
  await writeFile13(metaPath, patchCompositionMeta(rawMeta, dims), "utf8");
73292
73527
  const htmlPath = path32.join(compositionDir, "index.html");
73293
- const rawHtml = await readFile24(htmlPath, "utf8");
73528
+ const rawHtml = await readFile25(htmlPath, "utf8");
73294
73529
  await writeFile13(htmlPath, patchCompositionHtml(rawHtml, dims), "utf8");
73295
73530
  }
73296
73531
 
@@ -73373,7 +73608,7 @@ var scaffoldAdCommand = defineCommand135({
73373
73608
  const specPath = args8.spec;
73374
73609
  let parsed;
73375
73610
  try {
73376
- parsed = JSON.parse(await readFile25(specPath, "utf-8"));
73611
+ parsed = JSON.parse(await readFile26(specPath, "utf-8"));
73377
73612
  } catch (e) {
73378
73613
  writeJson({
73379
73614
  ok: false,
@@ -73534,7 +73769,7 @@ var scaffoldAdCommand = defineCommand135({
73534
73769
  ] : [];
73535
73770
  const markPath = spec.data.brand?.logo?.trim();
73536
73771
  if (!noBrand && markPath?.toLowerCase().endsWith(".svg")) {
73537
- const svg = await readFile25(markPath, "utf-8").catch(() => null);
73772
+ const svg = await readFile26(markPath, "utf-8").catch(() => null);
73538
73773
  if (svg === null) {
73539
73774
  writeJson({
73540
73775
  ok: false,
@@ -73614,10 +73849,10 @@ var scaffoldAdCommand = defineCommand135({
73614
73849
  await stampCompositionDims(compositionDest, outDims);
73615
73850
  await stampCompositionDims(captionsDest, outDims);
73616
73851
  const captionsIndex = path33.join(captionsDest, "index.html");
73617
- 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");
73618
73853
  const face = await stageBrandDisplayFace(outDir, captionsDest);
73619
73854
  if (face) {
73620
- 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");
73621
73856
  }
73622
73857
  const blueprintPath2 = path33.join(outDir, "prompt.json");
73623
73858
  const blueprintStylePath = path33.join(outDir, "prompt.style.json");
@@ -73710,7 +73945,7 @@ var scaffoldAdCommand = defineCommand135({
73710
73945
  }) : "";
73711
73946
  if (overlayHtml && renderedDir) {
73712
73947
  const indexPath = path33.join(renderedDir, "index.html");
73713
- const html = await readFile25(indexPath, "utf-8");
73948
+ const html = await readFile26(indexPath, "utf-8");
73714
73949
  await writeFile14(indexPath, injectAdBrandOverlay(html, overlayHtml, { aspect: outAr, ...face ? { face: AD_DISPLAY_FAMILY } : {} }), "utf-8");
73715
73950
  }
73716
73951
  await writeFile14(
@@ -73898,7 +74133,7 @@ async function stageBrandDisplayFace(outDir, compositionDir) {
73898
74133
  }
73899
74134
 
73900
74135
  // src/commands/canvas/scaffold-video.ts
73901
- 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";
73902
74137
  import { tmpdir as tmpdir3 } from "os";
73903
74138
  import path35 from "path";
73904
74139
  import { defineCommand as defineCommand136 } from "citty";
@@ -73940,7 +74175,7 @@ function routeVideoModel(input) {
73940
74175
 
73941
74176
  // src/engine/nodes/local/lib/sceneDetect.ts
73942
74177
  import { execFile as execFile3 } from "child_process";
73943
- 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";
73944
74179
  import { tmpdir as tmpdir2 } from "os";
73945
74180
  import { join as join3 } from "path";
73946
74181
  import { promisify as promisify3 } from "util";
@@ -74016,7 +74251,7 @@ async function runSceneDetectOnce(filePath, threshold, minSceneLenS, timeoutMs)
74016
74251
  );
74017
74252
  const csvName = (await readdir12(outDir)).find((f) => f.toLowerCase().endsWith(".csv"));
74018
74253
  if (!csvName) return [];
74019
- return parsePySceneDetectCsvCuts(await readFile26(join3(outDir, csvName), "utf-8"));
74254
+ return parsePySceneDetectCsvCuts(await readFile27(join3(outDir, csvName), "utf-8"));
74020
74255
  } finally {
74021
74256
  await rm7(outDir, { recursive: true, force: true });
74022
74257
  }
@@ -74040,7 +74275,7 @@ async function detectSceneCutsPySceneDetect(filePath, opts = {}) {
74040
74275
  }
74041
74276
 
74042
74277
  // src/commands/canvas/gitignore.ts
74043
- import { appendFile, readFile as readFile27 } from "fs/promises";
74278
+ import { appendFile, readFile as readFile28 } from "fs/promises";
74044
74279
  import path34 from "path";
74045
74280
  function missingGitignoreEntries(existing, entries) {
74046
74281
  const present2 = new Set(
@@ -74052,7 +74287,7 @@ async function ensureGitignore(dir, entries) {
74052
74287
  const file = path34.join(dir, ".gitignore");
74053
74288
  let existing;
74054
74289
  try {
74055
- existing = await readFile27(file, "utf8");
74290
+ existing = await readFile28(file, "utf8");
74056
74291
  } catch {
74057
74292
  return;
74058
74293
  }
@@ -74474,7 +74709,7 @@ var scaffoldVideoCommand = defineCommand136({
74474
74709
  await stampCompositionDims(compositionDest, outDims);
74475
74710
  const indexPath = path35.join(compositionDest, "index.html");
74476
74711
  const overlayHtml = buildOverlayHtml(blueprint, { captionsActive: Boolean(transcript), videoModel });
74477
- const indexHtml = await readFile28(indexPath, "utf8");
74712
+ const indexHtml = await readFile29(indexPath, "utf8");
74478
74713
  const injected = indexHtml.replace("<!--OVERLAYS-->", () => overlayHtml);
74479
74714
  if (injected === indexHtml && overlayHtml.trim()) {
74480
74715
  fail4(
@@ -74639,7 +74874,7 @@ var scaffoldVideoCommand = defineCommand136({
74639
74874
  });
74640
74875
 
74641
74876
  // src/commands/canvas/set-prompt.ts
74642
- import { readFile as readFile29, writeFile as writeFile16 } from "fs/promises";
74877
+ import { readFile as readFile30, writeFile as writeFile16 } from "fs/promises";
74643
74878
  import path36 from "path";
74644
74879
  import { defineCommand as defineCommand137 } from "citty";
74645
74880
  function setNodePrompt(canvas, nodeId, text2) {
@@ -74670,7 +74905,7 @@ var SetPromptError = class extends Error {
74670
74905
  };
74671
74906
  async function setPromptInCanvasFile(canvasPath, nodeId, text2) {
74672
74907
  const filePath = path36.resolve(canvasPath);
74673
- const original = await readFile29(filePath, "utf8");
74908
+ const original = await readFile30(filePath, "utf8");
74674
74909
  let canvas;
74675
74910
  try {
74676
74911
  canvas = JSON.parse(original);
@@ -74737,7 +74972,7 @@ var setPromptCommand = defineCommand137({
74737
74972
  },
74738
74973
  async run({ args: args8 }) {
74739
74974
  let text2;
74740
- 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");
74741
74976
  else if (args8.text !== void 0) text2 = String(args8.text);
74742
74977
  else {
74743
74978
  process.stderr.write(
@@ -75494,7 +75729,7 @@ import { Buffer as Buffer2 } from "buffer";
75494
75729
  import { defineCommand as defineCommand146 } from "citty";
75495
75730
 
75496
75731
  // src/commands/images/api.ts
75497
- import { readFile as readFile30 } from "fs/promises";
75732
+ import { readFile as readFile31 } from "fs/promises";
75498
75733
  import { basename, extname } from "path";
75499
75734
  var imageProcessingTimeoutMs = 18e4;
75500
75735
  var imageReadyPollIntervalMs = 2e3;
@@ -75508,7 +75743,7 @@ var mimeMap = {
75508
75743
  ".avif": "image/avif"
75509
75744
  };
75510
75745
  var defaultImageApiDeps = {
75511
- readFile: readFile30,
75746
+ readFile: readFile31,
75512
75747
  post: apiPost,
75513
75748
  get: apiGet,
75514
75749
  sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms))
@@ -75574,7 +75809,7 @@ async function waitForReadyImage(deps, imageId, opts = {}) {
75574
75809
  }
75575
75810
 
75576
75811
  // src/commands/videos/api.ts
75577
- import { readFile as readFile31 } from "fs/promises";
75812
+ import { readFile as readFile32 } from "fs/promises";
75578
75813
  import { basename as basename2, extname as extname2 } from "path";
75579
75814
  var videoProcessingTimeoutMs = 18e4;
75580
75815
  var videoReadyPollIntervalMs = 3e3;
@@ -75586,7 +75821,7 @@ var mimeMap2 = {
75586
75821
  ".mkv": "video/x-matroska"
75587
75822
  };
75588
75823
  var defaultVideoApiDeps = {
75589
- readFile: readFile31,
75824
+ readFile: readFile32,
75590
75825
  post: apiPost,
75591
75826
  get: apiGet,
75592
75827
  sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms)),
@@ -75977,11 +76212,11 @@ Full guide: __tooling__/docs/tools/baker/creatives.md`
75977
76212
  import { defineCommand as defineCommand149 } from "citty";
75978
76213
 
75979
76214
  // src/commands/experiment/composition.ts
75980
- import { readdir as readdir14, readFile as readFile33 } from "fs/promises";
76215
+ import { readdir as readdir14, readFile as readFile34 } from "fs/promises";
75981
76216
  import path40 from "path";
75982
76217
 
75983
76218
  // src/commands/landing/import-graph.ts
75984
- 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";
75985
76220
  import path39 from "path";
75986
76221
  var SOURCE_EXT = /\.(astro|ts|tsx|js|jsx|mjs|cjs|vue|svelte)$/;
75987
76222
  var RESOLVE_CANDIDATES = [
@@ -76058,7 +76293,7 @@ async function landingGraphFiles(root, slug) {
76058
76293
  if (file === void 0 || seen.has(file)) continue;
76059
76294
  let text2;
76060
76295
  try {
76061
- text2 = await readFile32(file, "utf8");
76296
+ text2 = await readFile33(file, "utf8");
76062
76297
  } catch {
76063
76298
  continue;
76064
76299
  }
@@ -76123,7 +76358,7 @@ async function readForms(root, slug) {
76123
76358
  const [files, flows] = await Promise.all([landingGraphFiles(root, slug), knownFlows(root)]);
76124
76359
  if (flows.size === 0) return [];
76125
76360
  const texts = await Promise.all(
76126
- [...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"))
76127
76362
  );
76128
76363
  return formsReferenced(texts, flows);
76129
76364
  } catch {
@@ -76133,7 +76368,7 @@ async function readForms(root, slug) {
76133
76368
 
76134
76369
  // src/commands/experiment/fold.ts
76135
76370
  import { statSync } from "fs";
76136
- 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";
76137
76372
  import path42 from "path";
76138
76373
 
76139
76374
  // ../api/src/experiments/variants.ts
@@ -76236,7 +76471,7 @@ async function archiveArm(opts) {
76236
76471
  if (!TEXT_EXT.test(rel)) {
76237
76472
  await cp3(abs, dest);
76238
76473
  } else {
76239
- const text2 = await readFile34(abs, "utf8");
76474
+ const text2 = await readFile35(abs, "utf8");
76240
76475
  await writeFile17(dest, repointIntoArchive(text2, abs, dest, archivedBySource), "utf8");
76241
76476
  }
76242
76477
  written.push(posix2(path42.relative(root, dest)));
@@ -76307,7 +76542,7 @@ async function promoteVariantFiles(opts) {
76307
76542
  if (!TEXT_EXT.test(rel)) {
76308
76543
  await cp3(from, to);
76309
76544
  } else {
76310
- const text2 = await readFile34(from, "utf8");
76545
+ const text2 = await readFile35(from, "utf8");
76311
76546
  await writeFile17(
76312
76547
  to,
76313
76548
  repointMoved(text2, {
@@ -77149,11 +77384,11 @@ function parseEvidence(raw, rawArgs) {
77149
77384
  }
77150
77385
 
77151
77386
  // src/commands/experiment/identity.ts
77152
- import { readFile as readFile35 } from "fs/promises";
77387
+ import { readFile as readFile36 } from "fs/promises";
77153
77388
  import path43 from "path";
77154
77389
  async function readPageInternalId(root, slug) {
77155
77390
  try {
77156
- 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");
77157
77392
  return /^internalId:\s*"?([^"\s]+)"?\s*$/m.exec(text2)?.[1];
77158
77393
  } catch {
77159
77394
  return void 0;
@@ -77161,7 +77396,7 @@ async function readPageInternalId(root, slug) {
77161
77396
  }
77162
77397
 
77163
77398
  // src/commands/experiment/measurement.ts
77164
- import { readFile as readFile36 } from "fs/promises";
77399
+ import { readFile as readFile37 } from "fs/promises";
77165
77400
  import path44 from "path";
77166
77401
  function eventDeclarationsIn(source) {
77167
77402
  const found = [];
@@ -77222,7 +77457,7 @@ function sectionsOf(files, slug) {
77222
77457
  return files.filter((file) => file.startsWith(prefix) && !file.slice(prefix.length).startsWith("index."));
77223
77458
  }
77224
77459
  async function declarationsOf(root, files) {
77225
- 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(() => "")));
77226
77461
  return sources.flatMap(eventDeclarationsIn);
77227
77462
  }
77228
77463
 
@@ -83102,7 +83337,7 @@ function cropSprite(input, region) {
83102
83337
 
83103
83338
  // src/lib/image/io.ts
83104
83339
  import { randomBytes } from "crypto";
83105
- 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";
83106
83341
  import { dirname as dirname2, extname as extname3, join as join5, resolve as resolve4 } from "path";
83107
83342
  var REMOTE_RE = /^https?:\/\//i;
83108
83343
  var GLOB_RE = /[*?[\]{}]/;
@@ -83135,7 +83370,7 @@ async function readImageBuffer(pathOrUrl) {
83135
83370
  const { buffer } = await fetchExternalBytes(pathOrUrl, { maxBytes: MAX_REMOTE_IMAGE_BYTES });
83136
83371
  return buffer;
83137
83372
  }
83138
- return readFile37(pathOrUrl);
83373
+ return readFile38(pathOrUrl);
83139
83374
  }
83140
83375
  async function isDirectory(path59) {
83141
83376
  try {
@@ -86684,7 +86919,7 @@ Full guide: __tooling__/docs/tools/baker/images.md`
86684
86919
  import { defineCommand as defineCommand207 } from "citty";
86685
86920
 
86686
86921
  // src/commands/landing/critique.ts
86687
- 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";
86688
86923
  import path47 from "path";
86689
86924
  import { defineCommand as defineCommand196 } from "citty";
86690
86925
 
@@ -87895,7 +88130,7 @@ async function writeCritiqueSnapshot(projectRoot, snapshot) {
87895
88130
  }
87896
88131
 
87897
88132
  // src/commands/landing/source-version.ts
87898
- 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";
87899
88134
  import path46 from "path";
87900
88135
  var CRITIQUED_ROOTS = ["src/pages/", "src/components/"];
87901
88136
  async function landingSourceRelPaths(root, slug) {
@@ -87905,7 +88140,7 @@ async function landingSourceRelPaths(root, slug) {
87905
88140
  async function readLandingSources(root, slug) {
87906
88141
  const rel = await landingSourceRelPaths(root, slug);
87907
88142
  const out = [];
87908
- 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") });
87909
88144
  return out;
87910
88145
  }
87911
88146
  async function computeLandingSourceSha(root, slug) {
@@ -87914,7 +88149,7 @@ async function computeLandingSourceSha(root, slug) {
87914
88149
  for (const r of rel) {
87915
88150
  let bytes;
87916
88151
  try {
87917
- bytes = await readFile38(path46.join(root, r));
88152
+ bytes = await readFile39(path46.join(root, r));
87918
88153
  } catch {
87919
88154
  bytes = Buffer.alloc(0);
87920
88155
  }
@@ -87933,7 +88168,7 @@ async function computeLegacyLandingSourceSha(landingDir) {
87933
88168
  for (const r of rel) {
87934
88169
  let bytes;
87935
88170
  try {
87936
- bytes = await readFile38(path46.join(landingDir, r));
88171
+ bytes = await readFile39(path46.join(landingDir, r));
87937
88172
  } catch {
87938
88173
  bytes = Buffer.alloc(0);
87939
88174
  }
@@ -88092,7 +88327,7 @@ async function readCompetitorNames(projectRoot) {
88092
88327
  for (const entry of entries) {
88093
88328
  names.push(path47.basename(entry, ".md").replace(/[-_]+/g, " "));
88094
88329
  try {
88095
- 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);
88096
88331
  const titled = /^\s*(?:title|name)\s*:\s*["']?([^"'\n]+)["']?\s*$/im.exec(head);
88097
88332
  if (titled?.[1]) names.push(titled[1].trim());
88098
88333
  } catch {
@@ -91295,7 +91530,7 @@ Full guide: __tooling__/docs/tools/baker/landing.md`
91295
91530
 
91296
91531
  // src/commands/landing/variant.ts
91297
91532
  import { randomUUID as randomUUID3 } from "crypto";
91298
- 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";
91299
91534
  import path55 from "path";
91300
91535
  import { defineCommand as defineCommand206 } from "citty";
91301
91536
  registerSchema({
@@ -91323,7 +91558,7 @@ registerSchema({
91323
91558
  var SLUG_RE2 = /^[a-z0-9][a-z0-9-]*$/;
91324
91559
  async function readInternalId(definitionPath) {
91325
91560
  try {
91326
- const text2 = await readFile40(definitionPath, "utf8");
91561
+ const text2 = await readFile41(definitionPath, "utf8");
91327
91562
  return /^internalId:\s*"?([^"\s]+)"?\s*$/m.exec(text2)?.[1] ?? null;
91328
91563
  } catch {
91329
91564
  return null;
@@ -91482,7 +91717,7 @@ async function writeVariant(opts) {
91482
91717
  const { controlDir, variantDir, controlSlug, variantSlug: variantSlug2, forks } = opts;
91483
91718
  const forkedTargets = new Set(forks.map((f) => path55.join(controlDir, "_components", f)));
91484
91719
  const written = [];
91485
- const indexText = await readFile40(path55.join(controlDir, "index.astro"), "utf8");
91720
+ const indexText = await readFile41(path55.join(controlDir, "index.astro"), "utf8");
91486
91721
  await mkdir16(variantDir, { recursive: true });
91487
91722
  await writeFile24(
91488
91723
  path55.join(variantDir, "index.astro"),
@@ -91493,7 +91728,7 @@ async function writeVariant(opts) {
91493
91728
  for (const fork of forks) {
91494
91729
  const fromFile = path55.join(controlDir, "_components", fork);
91495
91730
  const toFile = path55.join(variantDir, "_components", fork);
91496
- const text2 = await readFile40(fromFile, "utf8");
91731
+ const text2 = await readFile41(fromFile, "utf8");
91497
91732
  await mkdir16(path55.dirname(toFile), { recursive: true });
91498
91733
  await writeFile24(
91499
91734
  toFile,
@@ -91510,7 +91745,7 @@ async function writeVariant(opts) {
91510
91745
  }
91511
91746
  const controlDefinitionPath = path55.join(controlDir, "_definition.md");
91512
91747
  if (await exists2(controlDefinitionPath)) {
91513
- const definition = buildVariantDefinition(await readFile40(controlDefinitionPath, "utf8"), {
91748
+ const definition = buildVariantDefinition(await readFile41(controlDefinitionPath, "utf8"), {
91514
91749
  internalId: randomUUID3().replace(/-/g, "").slice(0, 8),
91515
91750
  variantSlug: variantSlug2,
91516
91751
  controlSlug,
@@ -94227,7 +94462,7 @@ Full guide: __tooling__/docs/tools/baker/scheduled-agents.md`
94227
94462
  });
94228
94463
 
94229
94464
  // src/commands/scheduled-agents/templates.ts
94230
- import { readFile as readFile41 } from "fs/promises";
94465
+ import { readFile as readFile42 } from "fs/promises";
94231
94466
  import path56 from "path";
94232
94467
  import { defineCommand as defineCommand234 } from "citty";
94233
94468
  registerSchema({
@@ -94335,7 +94570,7 @@ Full guide: __tooling__/docs/tools/baker/scheduled-agents.md`
94335
94570
  }
94336
94571
  if (save.length > 0) {
94337
94572
  const briefFile = flag("brief-file");
94338
- 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");
94339
94574
  if (brief2.trim().length === 0) {
94340
94575
  failValidation4("--brief-file (preferred) or --brief is required: the brief is the recipe.");
94341
94576
  }
@@ -98065,7 +98300,7 @@ Full guide: __tooling__/docs/tools/baker/videos.md`
98065
98300
  });
98066
98301
 
98067
98302
  // src/commands/winning-ads/index.ts
98068
- import { defineCommand as defineCommand279 } from "citty";
98303
+ import { defineCommand as defineCommand277 } from "citty";
98069
98304
 
98070
98305
  // src/commands/winning-ads/advertisers.ts
98071
98306
  import { defineCommand as defineCommand267 } from "citty";
@@ -98178,90 +98413,8 @@ var advertisersCommand2 = defineCommand267({
98178
98413
  }
98179
98414
  });
98180
98415
 
98181
- // src/commands/winning-ads/brief.ts
98182
- import { defineCommand as defineCommand268 } from "citty";
98183
- registerSchema({
98184
- command: "winning-ads.brief",
98185
- description: "Generate a creative brief grounded in strategically-similar winning ads. Optionally describe the target creative with --dna (JSON) and steer with --notes.",
98186
- args: {
98187
- dna: {
98188
- type: "string",
98189
- description: `Partial target DNA as JSON, e.g. '{"angle":"cost savings","awareness_stage":"solution_aware"}'`,
98190
- required: false
98191
- },
98192
- notes: { type: "string", description: "Free-text guidance for the brief", required: false },
98193
- k: {
98194
- type: "number",
98195
- description: "Number of reference ads to pull 1-50 (default: service default)",
98196
- required: false
98197
- }
98198
- }
98199
- });
98200
- function buildBriefBody(args8) {
98201
- const body = {};
98202
- if (args8.targetDna && Object.keys(args8.targetDna).length) {
98203
- body.target_dna = args8.targetDna;
98204
- }
98205
- if (args8.notes) {
98206
- body.notes = args8.notes;
98207
- }
98208
- if (args8.k !== void 0 && args8.k !== "") {
98209
- body.k_reference_ads = Number(args8.k);
98210
- }
98211
- return body;
98212
- }
98213
- function parseDna(raw) {
98214
- if (!raw) {
98215
- return void 0;
98216
- }
98217
- let parsed;
98218
- try {
98219
- parsed = JSON.parse(raw);
98220
- } catch {
98221
- throw new SyntaxError("--dna must be valid JSON");
98222
- }
98223
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
98224
- throw new SyntaxError("--dna must be a JSON object");
98225
- }
98226
- return parsed;
98227
- }
98228
- var briefCommand = defineCommand268({
98229
- meta: {
98230
- name: "brief",
98231
- description: `Generate a creative brief from winning references. Example: baker winning-ads brief --dna '{"angle":"cost savings"}' --notes "B2B, LinkedIn video" --k 8`
98232
- },
98233
- args: {
98234
- dna: { type: "string", description: "Partial target DNA as JSON", required: false },
98235
- notes: { type: "string", description: "Free-text guidance", required: false },
98236
- k: { type: "string", description: "Number of reference ads (1-50)", required: false }
98237
- },
98238
- run: async ({ args: args8 }) => {
98239
- try {
98240
- let targetDna;
98241
- try {
98242
- targetDna = parseDna(args8.dna);
98243
- } catch (err) {
98244
- writeJson({
98245
- ok: false,
98246
- error: { code: "VALIDATION_ERROR", message: err instanceof Error ? err.message : "Invalid --dna" }
98247
- });
98248
- process.exit(1);
98249
- }
98250
- const body = buildBriefBody({
98251
- targetDna,
98252
- notes: args8.notes,
98253
- k: args8.k
98254
- });
98255
- const data = await apiPost("/api/ad-library/brief", body);
98256
- writeJson({ ok: true, data });
98257
- } catch (err) {
98258
- reportError3(err);
98259
- }
98260
- }
98261
- });
98262
-
98263
98416
  // src/commands/winning-ads/content.ts
98264
- import { defineCommand as defineCommand269 } from "citty";
98417
+ import { defineCommand as defineCommand268 } from "citty";
98265
98418
  registerSchema({
98266
98419
  command: "winning-ads.content",
98267
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.",
@@ -98274,7 +98427,7 @@ registerSchema({
98274
98427
  }
98275
98428
  }
98276
98429
  });
98277
- var contentCommand = defineCommand269({
98430
+ var contentCommand = defineCommand268({
98278
98431
  meta: {
98279
98432
  name: "content",
98280
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"
@@ -98323,7 +98476,7 @@ var contentCommand = defineCommand269({
98323
98476
  });
98324
98477
 
98325
98478
  // src/commands/winning-ads/feed.ts
98326
- import { defineCommand as defineCommand270 } from "citty";
98479
+ import { defineCommand as defineCommand269 } from "citty";
98327
98480
  function buildFeedParams(input) {
98328
98481
  const params = {};
98329
98482
  const advertiser = splitList4(input.advertiser);
@@ -98375,7 +98528,7 @@ registerSchema({
98375
98528
  format: { type: "string", description: "Comma-separated formats to include (e.g. static,video)", required: false }
98376
98529
  }
98377
98530
  });
98378
- var feedCommand2 = defineCommand270({
98531
+ var feedCommand2 = defineCommand269({
98379
98532
  meta: {
98380
98533
  name: "feed",
98381
98534
  description: "Winners across every brand you follow (browse, then trim per advertiser). Example: baker winning-ads feed --per-advertiser 5 --output md"
@@ -98460,7 +98613,7 @@ var feedCommand2 = defineCommand270({
98460
98613
  });
98461
98614
 
98462
98615
  // src/commands/winning-ads/follow.ts
98463
- import { defineCommand as defineCommand271 } from "citty";
98616
+ import { defineCommand as defineCommand270 } from "citty";
98464
98617
  var PLATFORMS = adLibraryPlatformSchema.options;
98465
98618
  registerSchema({
98466
98619
  command: "winning-ads.follow",
@@ -98475,7 +98628,7 @@ registerSchema({
98475
98628
  label: { type: "string", description: "Optional display label (defaults to the resolved name)", required: false }
98476
98629
  }
98477
98630
  });
98478
- var followCommand = defineCommand271({
98631
+ var followCommand = defineCommand270({
98479
98632
  meta: {
98480
98633
  name: "follow",
98481
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'
@@ -98522,7 +98675,7 @@ var followCommand = defineCommand271({
98522
98675
  });
98523
98676
 
98524
98677
  // src/commands/winning-ads/follow-competitors.ts
98525
- import { defineCommand as defineCommand272 } from "citty";
98678
+ import { defineCommand as defineCommand271 } from "citty";
98526
98679
  var PLATFORMS2 = adLibraryPlatformSchema.options;
98527
98680
  var BATCH_TIMEOUT_MS = 3e5;
98528
98681
  function buildFollowBatchBody(input) {
@@ -98555,7 +98708,7 @@ registerSchema({
98555
98708
  }
98556
98709
  }
98557
98710
  });
98558
- var followCompetitorsCommand = defineCommand272({
98711
+ var followCompetitorsCommand = defineCommand271({
98559
98712
  meta: {
98560
98713
  name: "follow-competitors",
98561
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"'
@@ -98630,7 +98783,7 @@ var followCompetitorsCommand = defineCommand272({
98630
98783
  });
98631
98784
 
98632
98785
  // src/commands/winning-ads/following.ts
98633
- import { defineCommand as defineCommand273 } from "citty";
98786
+ import { defineCommand as defineCommand272 } from "citty";
98634
98787
  registerSchema({
98635
98788
  command: "winning-ads.following",
98636
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.",
@@ -98684,7 +98837,7 @@ function followingNormalizer(record, full) {
98684
98837
  platforms: Array.isArray(record.platforms) ? record.platforms : []
98685
98838
  };
98686
98839
  }
98687
- var followingCommand = defineCommand273({
98840
+ var followingCommand = defineCommand272({
98688
98841
  meta: {
98689
98842
  name: "following",
98690
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"
@@ -98719,113 +98872,18 @@ var followingCommand = defineCommand273({
98719
98872
  }
98720
98873
  });
98721
98874
 
98722
- // src/commands/winning-ads/patterns.ts
98723
- import { defineCommand as defineCommand274 } from "citty";
98724
- registerSchema({
98725
- command: "winning-ads.patterns",
98726
- 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.",
98727
- args: {
98728
- winners: { type: "string", description: "Comma list of winning ad ids (cohort A)", required: true },
98729
- duds: { type: "string", description: "Comma list of weaker/dud ad ids (cohort B)", required: true },
98730
- "top-n": {
98731
- type: "number",
98732
- description: "Max discriminators to return 1-50 (default: service default)",
98733
- required: false
98734
- }
98735
- }
98736
- });
98737
- function buildPatternsBody(args8) {
98738
- const winners = splitList4(args8.winners);
98739
- const duds = splitList4(args8.duds);
98740
- if (!winners.length || !duds.length) {
98741
- throw new Error("Provide at least one ad id for both --winners and --duds");
98742
- }
98743
- const body = {
98744
- cohort_a: { ad_ids: winners },
98745
- cohort_b: { ad_ids: duds }
98746
- };
98747
- if (args8.topN !== void 0 && args8.topN !== "") {
98748
- body.top_n = Number(args8.topN);
98749
- }
98750
- return body;
98751
- }
98752
- function discriminatorRow(record) {
98753
- const dir = record.direction === "favors_a" ? "winners" : "duds";
98754
- return {
98755
- field: String(record.field ?? ""),
98756
- favors: dir,
98757
- score: record.score ?? null,
98758
- top_values_winners: Array.isArray(record.top_values_a) ? record.top_values_a.join(", ") : "",
98759
- top_values_duds: Array.isArray(record.top_values_b) ? record.top_values_b.join(", ") : ""
98760
- };
98761
- }
98762
- var patternsCommand = defineCommand274({
98763
- meta: {
98764
- name: "patterns",
98765
- 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"
98766
- },
98767
- args: {
98768
- winners: { type: "string", description: "Comma list of winning ad ids (cohort A)", required: true },
98769
- duds: { type: "string", description: "Comma list of weaker/dud ad ids (cohort B)", required: true },
98770
- "top-n": { type: "string", description: "Max discriminators (1-50)", required: false },
98771
- output: { type: "string", description: "Output format: json|files|md", required: false, default: "json" },
98772
- fields: { type: "string", description: "Comma-separated field names to include", required: false }
98773
- },
98774
- run: async ({ args: args8 }) => {
98775
- try {
98776
- let body;
98777
- try {
98778
- body = buildPatternsBody({
98779
- winners: args8.winners,
98780
- duds: args8.duds,
98781
- topN: args8["top-n"]
98782
- });
98783
- } catch (err) {
98784
- writeJson({
98785
- ok: false,
98786
- error: { code: "VALIDATION_ERROR", message: err instanceof Error ? err.message : "Invalid cohorts" }
98787
- });
98788
- process.exit(1);
98789
- }
98790
- const data = await apiPost("/api/ad-library/patterns", body);
98791
- const output = args8.output || "json";
98792
- const rawDiscriminators = Array.isArray(data?.discriminators) ? data.discriminators : [];
98793
- if (output === "json") {
98794
- writeJson({
98795
- ok: true,
98796
- data: {
98797
- cohort_a_size: data?.cohort_a_size ?? 0,
98798
- cohort_b_size: data?.cohort_b_size ?? 0,
98799
- discriminators: rawDiscriminators.map((d) => discriminatorRow(d))
98800
- }
98801
- });
98802
- return;
98803
- }
98804
- writeOutput(
98805
- { ok: true, data: rawDiscriminators },
98806
- output,
98807
- args8.fields ? args8.fields.split(",") : void 0,
98808
- false,
98809
- (record) => discriminatorRow(record)
98810
- );
98811
- } catch (err) {
98812
- reportError3(err);
98813
- }
98814
- }
98815
- });
98816
-
98817
98875
  // src/commands/winning-ads/search.ts
98818
- import { defineCommand as defineCommand275 } from "citty";
98876
+ import { defineCommand as defineCommand273 } from "citty";
98819
98877
  registerSchema({
98820
98878
  command: "winning-ads.search",
98821
- 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.",
98822
98880
  args: {
98823
98881
  query: { type: "string", description: "Free-text natural-language query", required: false },
98824
98882
  scope: {
98825
98883
  type: "string",
98826
- 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.",
98827
98885
  required: false,
98828
- default: "all"
98886
+ default: "followed-first"
98829
98887
  },
98830
98888
  "ref-ad-id": {
98831
98889
  type: "string",
@@ -98840,6 +98898,12 @@ registerSchema({
98840
98898
  default: 3
98841
98899
  },
98842
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
+ },
98843
98907
  platform: {
98844
98908
  type: "string",
98845
98909
  description: "Comma list: meta,linkedin,tiktok",
@@ -98928,6 +98992,7 @@ function buildSearchBody2(args8) {
98928
98992
  setNumber(body, "limit", args8.limit);
98929
98993
  setNumber(body, "max_per_advertiser", args8.maxPerAdvertiser);
98930
98994
  setNumber(body, "min_relevance", args8.minRelevance);
98995
+ setString(body, "sort", args8.sort);
98931
98996
  if (args8.diverse) {
98932
98997
  body.diverse = true;
98933
98998
  }
@@ -99048,6 +99113,9 @@ function searchRejection(args8, body) {
99048
99113
  if (args8.scope !== void 0 && !winnerSearchScopeSchema.safeParse(args8.scope).success) {
99049
99114
  return { message: `Unknown --scope "${args8.scope}". Use one of: ${winnerSearchScopeSchema.options.join(", ")}.` };
99050
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
+ }
99051
99119
  for (const [flag, key] of NUMERIC_FLAGS) {
99052
99120
  const value2 = args8[key];
99053
99121
  if (value2 !== void 0 && value2 !== "" && Number.isNaN(Number(value2))) {
@@ -99083,16 +99151,16 @@ function writeSearchResult(data, opts) {
99083
99151
  writeOutput({ ok: true, data: rawResults, ...withHints2 }, opts.output, opts.fields, opts.full, winningAdNormalizer);
99084
99152
  }
99085
99153
  var SEARCH_TIMEOUT_MS = 12e4;
99086
- var searchCommand5 = defineCommand275({
99154
+ var searchCommand5 = defineCommand273({
99087
99155
  meta: {
99088
99156
  name: "search",
99089
- 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."
99090
99158
  },
99091
99159
  args: {
99092
99160
  query: { type: "positional", description: "Free-text search query", required: false },
99093
99161
  scope: {
99094
99162
  type: "string",
99095
- 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)",
99096
99164
  required: false
99097
99165
  },
99098
99166
  "ref-ad-id": { type: "string", description: "Find ads similar to this ad id", required: false },
@@ -99108,6 +99176,11 @@ var searchCommand5 = defineCommand275({
99108
99176
  required: false
99109
99177
  },
99110
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
+ },
99111
99184
  platform: {
99112
99185
  type: "string",
99113
99186
  description: "Comma list of platforms (meta,linkedin,tiktok) \u2014 search one or several",
@@ -99154,7 +99227,7 @@ var searchCommand5 = defineCommand275({
99154
99227
  fields: { type: "string", description: "Comma-separated field names to include", required: false },
99155
99228
  full: {
99156
99229
  type: "boolean",
99157
- 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",
99158
99231
  required: false,
99159
99232
  default: false
99160
99233
  }
@@ -99168,6 +99241,7 @@ var searchCommand5 = defineCommand275({
99168
99241
  limit: args8.limit,
99169
99242
  maxPerAdvertiser: args8["max-per-advertiser"],
99170
99243
  minRelevance: args8["min-relevance"],
99244
+ sort: args8.sort,
99171
99245
  platform: args8.platform,
99172
99246
  format: args8.format,
99173
99247
  winnerCategory: args8["winner-category"],
@@ -99203,7 +99277,7 @@ var searchCommand5 = defineCommand275({
99203
99277
  });
99204
99278
 
99205
99279
  // src/commands/winning-ads/seeds.ts
99206
- import { defineCommand as defineCommand276 } from "citty";
99280
+ import { defineCommand as defineCommand274 } from "citty";
99207
99281
  function leanRow(r) {
99208
99282
  return {
99209
99283
  key: r.key,
@@ -99231,7 +99305,7 @@ function makeSeedCommand(opts) {
99231
99305
  limit: { type: "number", description: "Max keys 1-100 (default 20)", required: false, default: 20 }
99232
99306
  }
99233
99307
  });
99234
- return defineCommand276({
99308
+ return defineCommand274({
99235
99309
  meta: { name: opts.name, description: opts.description },
99236
99310
  args: {
99237
99311
  platform: { type: "string", description: "Single platform to segment on", required: false },
@@ -99280,7 +99354,7 @@ var formatsCommand = makeSeedCommand({
99280
99354
  });
99281
99355
 
99282
99356
  // src/commands/winning-ads/unfollow.ts
99283
- import { defineCommand as defineCommand277 } from "citty";
99357
+ import { defineCommand as defineCommand275 } from "citty";
99284
99358
  registerSchema({
99285
99359
  command: "winning-ads.unfollow",
99286
99360
  description: "Stop following a brand \u2014 removes it from your ad-dna library by advertiser id.",
@@ -99288,7 +99362,7 @@ registerSchema({
99288
99362
  advertiser: { type: "string", description: "Advertiser id to unfollow", required: true }
99289
99363
  }
99290
99364
  });
99291
- var unfollowCommand = defineCommand277({
99365
+ var unfollowCommand = defineCommand275({
99292
99366
  meta: {
99293
99367
  name: "unfollow",
99294
99368
  description: "Stop following a brand by advertiser id. Example: baker winning-ads unfollow adv_123"
@@ -99309,7 +99383,7 @@ var unfollowCommand = defineCommand277({
99309
99383
  });
99310
99384
 
99311
99385
  // src/commands/winning-ads/winners.ts
99312
- import { defineCommand as defineCommand278 } from "citty";
99386
+ import { defineCommand as defineCommand276 } from "citty";
99313
99387
  registerSchema({
99314
99388
  command: "winning-ads.winners",
99315
99389
  description: "Top winning ads for one advertiser id (from `advertisers` or `following`). Returns lean winner cards; add --full for DNA + longevity.",
@@ -99319,7 +99393,7 @@ registerSchema({
99319
99393
  platform: { type: "string", description: "Filter to a single platform: meta|linkedin|tiktok", required: false }
99320
99394
  }
99321
99395
  });
99322
- var winnersCommand = defineCommand278({
99396
+ var winnersCommand = defineCommand276({
99323
99397
  meta: {
99324
99398
  name: "winners",
99325
99399
  description: "Top winning ads for a specific advertiser id. Example: baker winning-ads winners adv_123 --top 15 --output md"
@@ -99369,7 +99443,7 @@ var winnersCommand = defineCommand278({
99369
99443
  });
99370
99444
 
99371
99445
  // src/commands/winning-ads/index.ts
99372
- var winningAdsCommand = defineCommand279({
99446
+ var winningAdsCommand = defineCommand277({
99373
99447
  meta: {
99374
99448
  name: "winning-ads",
99375
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.
@@ -99377,7 +99451,7 @@ var winningAdsCommand = defineCommand279({
99377
99451
  Auth: BAKER_API_KEY (bk_ or bkr_) + BAKER_API_URL (your Convex .convex.site URL).
99378
99452
 
99379
99453
  Subcommands:
99380
- 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)
99381
99455
  baker winning-ads advertisers "<brand>" \u2014 list corpus brands \u2192 advertiser ids (for --exclude-advertiser / --advertiser-id)
99382
99456
  baker winning-ads follow "<domain|url|brand>" --platform meta|linkedin|tiktok \u2014 add a brand's ads to your library
99383
99457
  baker winning-ads follow-competitors "<domain,domain,...>" \u2014 add MANY brands at once (every competitor by domain)
@@ -99386,8 +99460,6 @@ Subcommands:
99386
99460
  baker winning-ads winners <advertiser> \u2014 top winners for one advertiser id
99387
99461
  baker winning-ads content <ad-id> \u2014 read ONE ad's transcript + on-screen text + copy (understand a reference before reproducing it)
99388
99462
  baker winning-ads unfollow <advertiser> \u2014 stop following a brand
99389
- baker winning-ads brief \u2014 creative brief grounded in similar winners
99390
- baker winning-ads patterns --winners \u2026 --duds \u2026 \u2014 what separates a winning cohort from a weak one
99391
99463
  baker winning-ads hooks \u2014 top winner-weighted HOOK seeds for a segment (feed the seed to search --hook-archetype)
99392
99464
  baker winning-ads mechanisms \u2014 top winner-weighted PRODUCT mechanism seeds (how the product claims to work)
99393
99465
  baker winning-ads formats \u2014 top winner-weighted static FORMAT patterns for a segment
@@ -99403,7 +99475,7 @@ Examples:
99403
99475
  baker winning-ads content adg_123 --full --output md
99404
99476
  baker winning-ads hooks --platform meta --awareness problem_aware --industry saas --output md
99405
99477
  baker winning-ads search "fintech onboarding" --hook-archetype callout --winner-category winner --output md
99406
- 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
99407
99479
  Full guide: __tooling__/docs/tools/baker/winning-ads.md`
99408
99480
  },
99409
99481
  subCommands: {
@@ -99416,8 +99488,6 @@ Full guide: __tooling__/docs/tools/baker/winning-ads.md`
99416
99488
  winners: winnersCommand,
99417
99489
  content: contentCommand,
99418
99490
  unfollow: unfollowCommand,
99419
- brief: briefCommand,
99420
- patterns: patternsCommand,
99421
99491
  hooks: hooksCommand,
99422
99492
  mechanisms: mechanismsCommand,
99423
99493
  formats: formatsCommand
@@ -99441,7 +99511,7 @@ function getCliVersion() {
99441
99511
  }
99442
99512
 
99443
99513
  // src/cli.ts
99444
- var main = defineCommand280({
99514
+ var main = defineCommand278({
99445
99515
  meta: {
99446
99516
  name: "baker",
99447
99517
  version: getCliVersion(),