@koda-sl/baker-cli 0.139.0-dev.da48e17cb → 0.139.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -34,7 +34,7 @@ import {
34
34
  toModelSafeImage,
35
35
  ulid,
36
36
  validateCanvasDeep
37
- } from "./chunk-TXNOPO6P.js";
37
+ } from "./chunk-TAGQCU36.js";
38
38
  import {
39
39
  csvOrJson,
40
40
  daysAgoIso,
@@ -73,7 +73,7 @@ import {
73
73
  } from "./chunk-RK67WL4O.js";
74
74
 
75
75
  // src/cli.ts
76
- import { defineCommand as defineCommand176, runMain } from "citty";
76
+ import { defineCommand as defineCommand174, runMain } from "citty";
77
77
 
78
78
  // src/commands/actions/index.ts
79
79
  import { defineCommand as defineCommand18 } from "citty";
@@ -3602,9 +3602,9 @@ var unlinkCommand = defineCommand16({
3602
3602
  import { defineCommand as defineCommand17 } from "citty";
3603
3603
  registerSchema({
3604
3604
  command: "actions.update",
3605
- description: "Stage an update on an action (name, description, tags, and/or priority) \u2014 no claim needed; edit any action you're not working on. Applies on publish. Blocked only if another chat currently has it claimed. --tags REPLACES the tag set; pass --tags '' to clear. --priority accepts urgent|high|medium|low or 'none' to clear back to unset (== normal).",
3605
+ description: "Stage an update on an action (name, description, tags, and/or priority) \u2014 no claim needed; edit any action you're not working on. Accepts either a real action ID or a tempId from `baker actions create` in THIS chat (edits the still-staged task in place \u2014 use this to flesh out a task you just created instead of removing and recreating it). Applies on publish. Blocked only if another chat currently has a real action claimed. --tags REPLACES the tag set; pass --tags '' to clear. --priority accepts urgent|high|medium|low or 'none' to clear back to unset (== normal).",
3606
3606
  args: {
3607
- id: { type: "string", description: "Action ID", required: true },
3607
+ id: { type: "string", description: "Action ID or tempId (temp_*) of a task staged in this chat", required: true },
3608
3608
  name: { type: "string", description: "New name", required: false },
3609
3609
  description: { type: "string", description: "New description", required: false },
3610
3610
  tags: {
@@ -3622,11 +3622,11 @@ registerSchema({
3622
3622
  var updateCommand = defineCommand17({
3623
3623
  meta: {
3624
3624
  name: "update",
3625
- description: 'Stage an update on an action (no claim needed). Example: baker actions update <id> --name "New name"'
3625
+ description: 'Stage an update on an action, or edit a task still staged in this chat by its tempId (no claim needed). Example: baker actions update <id-or-tempId> --description "\u2026"'
3626
3626
  },
3627
3627
  args: {
3628
- id: { type: "positional", description: "Action ID", required: false },
3629
- "action-id": { type: "string", description: "Action ID", required: false },
3628
+ id: { type: "positional", description: "Action ID or tempId (temp_*)", required: false },
3629
+ "action-id": { type: "string", description: "Action ID or tempId (temp_*)", required: false },
3630
3630
  name: { type: "string", description: "New name", required: false },
3631
3631
  description: { type: "string", description: "New description", required: false },
3632
3632
  tags: { type: "string", description: "Comma-separated tag slugs \u2014 REPLACES existing ('' clears)", required: false },
@@ -3638,7 +3638,9 @@ var updateCommand = defineCommand17({
3638
3638
  if (!id) {
3639
3639
  failValidation("Action ID is required.");
3640
3640
  }
3641
- validateConvexId(id);
3641
+ if (!isTempId(id)) {
3642
+ validateConvexId(id);
3643
+ }
3642
3644
  const tags = parseTagList(args.tags);
3643
3645
  const priority = parsePriority(args.priority, { allowClear: true });
3644
3646
  if (args.name === void 0 && args.description === void 0 && tags === void 0 && priority === void 0) {
@@ -5455,11 +5457,11 @@ function microsFlag(value, flag) {
5455
5457
  if (value === void 0 || value === null || value === "") {
5456
5458
  return void 0;
5457
5459
  }
5458
- const num2 = Number(value);
5459
- if (Number.isNaN(num2) || num2 <= 0) {
5460
+ const num = Number(value);
5461
+ if (Number.isNaN(num) || num <= 0) {
5460
5462
  failWriteValidation(`${flag} must be a positive amount (major currency units, e.g. 50 or 50.00)`);
5461
5463
  }
5462
- return toMicros(num2);
5464
+ return toMicros(num);
5463
5465
  }
5464
5466
  function listFlag(value) {
5465
5467
  if (typeof value !== "string" || value.length === 0) {
@@ -5487,11 +5489,11 @@ function rawTextEntries(value) {
5487
5489
  const values = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
5488
5490
  return values.filter((v) => typeof v === "string").flatMap((v) => v.split(",")).map((v) => v.trim()).filter(Boolean);
5489
5491
  }
5490
- function rawFileEntries(path27) {
5491
- if (typeof path27 !== "string" || path27.length === 0) {
5492
+ function rawFileEntries(path23) {
5493
+ if (typeof path23 !== "string" || path23.length === 0) {
5492
5494
  return [];
5493
5495
  }
5494
- return readFileSync2(path27, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
5496
+ return readFileSync2(path23, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
5495
5497
  }
5496
5498
  function keywordEntries(args) {
5497
5499
  const defaultMatch = typeof args["match-type"] === "string" ? args["match-type"].toUpperCase() : void 0;
@@ -5514,19 +5516,19 @@ function keywordEntries(args) {
5514
5516
  }
5515
5517
  return entries;
5516
5518
  }
5517
- function loadJsonFileArg(path27) {
5518
- if (typeof path27 !== "string" || path27.length === 0) {
5519
+ function loadJsonFileArg(path23) {
5520
+ if (typeof path23 !== "string" || path23.length === 0) {
5519
5521
  return {};
5520
5522
  }
5521
5523
  try {
5522
- const parsed = JSON.parse(readFileSync2(path27, "utf8"));
5524
+ const parsed = JSON.parse(readFileSync2(path23, "utf8"));
5523
5525
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
5524
- failWriteValidation(`${path27} must contain a JSON object`);
5526
+ failWriteValidation(`${path23} must contain a JSON object`);
5525
5527
  }
5526
5528
  return parsed;
5527
5529
  } catch (err) {
5528
5530
  if (err instanceof SyntaxError) {
5529
- failWriteValidation(`${path27} is not valid JSON: ${err.message}`);
5531
+ failWriteValidation(`${path23} is not valid JSON: ${err.message}`);
5530
5532
  }
5531
5533
  throw err;
5532
5534
  }
@@ -5637,10 +5639,10 @@ async function stageUpdate(kind, customerId, target, payload) {
5637
5639
  async function stageTarget(kind, customerId, target) {
5638
5640
  await stageGoogleOp({ kind, customerId, target });
5639
5641
  }
5640
- async function draftAction(path27, body) {
5642
+ async function draftAction(path23, body) {
5641
5643
  try {
5642
5644
  const chatId = requireChatId();
5643
- const response = await apiPost(path27, { chatId, ...body });
5645
+ const response = await apiPost(path23, { chatId, ...body });
5644
5646
  writeJsonEnvelope(response);
5645
5647
  } catch (err) {
5646
5648
  handleGoogleError(err);
@@ -9203,19 +9205,19 @@ function failWriteValidation2(message) {
9203
9205
  writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
9204
9206
  process.exit(1);
9205
9207
  }
9206
- function loadJsonFileArg2(path27) {
9207
- if (typeof path27 !== "string" || path27.length === 0) {
9208
+ function loadJsonFileArg2(path23) {
9209
+ if (typeof path23 !== "string" || path23.length === 0) {
9208
9210
  return {};
9209
9211
  }
9210
9212
  try {
9211
- const parsed = JSON.parse(readFileSync6(path27, "utf8"));
9213
+ const parsed = JSON.parse(readFileSync6(path23, "utf8"));
9212
9214
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
9213
- failWriteValidation2(`${path27} must contain a JSON object`);
9215
+ failWriteValidation2(`${path23} must contain a JSON object`);
9214
9216
  }
9215
9217
  return parsed;
9216
9218
  } catch (err) {
9217
9219
  if (err instanceof SyntaxError) {
9218
- failWriteValidation2(`${path27} is not valid JSON: ${err.message}`);
9220
+ failWriteValidation2(`${path23} is not valid JSON: ${err.message}`);
9219
9221
  }
9220
9222
  throw err;
9221
9223
  }
@@ -9300,15 +9302,15 @@ function parseLocaleFlag(value) {
9300
9302
  }
9301
9303
  return { language: match[1], country: match[2].toUpperCase() };
9302
9304
  }
9303
- function loadTargetingFileArg(path27) {
9304
- if (typeof path27 !== "string" || path27.length === 0) {
9305
+ function loadTargetingFileArg(path23) {
9306
+ if (typeof path23 !== "string" || path23.length === 0) {
9305
9307
  return void 0;
9306
9308
  }
9307
- const parsed = loadJsonFileArg2(path27);
9309
+ const parsed = loadJsonFileArg2(path23);
9308
9310
  const criteria = parsed.targetingCriteria ?? parsed;
9309
9311
  if (!criteria.include) {
9310
9312
  failWriteValidation2(
9311
- `${path27} must contain targeting criteria with an "include" block (see baker schema ads.linkedin.campaigns.create)`
9313
+ `${path23} must contain targeting criteria with an "include" block (see baker schema ads.linkedin.campaigns.create)`
9312
9314
  );
9313
9315
  }
9314
9316
  return criteria;
@@ -9343,14 +9345,14 @@ function parseCsvLine(line) {
9343
9345
  cells.push(current);
9344
9346
  return cells.map((cell) => cell.trim());
9345
9347
  }
9346
- function parseListFileArg(path27, maxRows) {
9347
- if (typeof path27 !== "string" || path27.length === 0) {
9348
+ function parseListFileArg(path23, maxRows) {
9349
+ if (typeof path23 !== "string" || path23.length === 0) {
9348
9350
  return void 0;
9349
9351
  }
9350
- const raw = readFileSync6(path27, "utf8");
9352
+ const raw = readFileSync6(path23, "utf8");
9351
9353
  const lines = raw.split(/\r?\n/).filter((line) => line.trim().length > 0);
9352
9354
  if (lines.length < 2) {
9353
- failWriteValidation2(`${path27} needs a header row and at least one data row`);
9355
+ failWriteValidation2(`${path23} needs a header row and at least one data row`);
9354
9356
  }
9355
9357
  const columns = parseCsvLine(lines[0]).map((column) => column.trim());
9356
9358
  const rows = [];
@@ -9369,7 +9371,7 @@ function parseListFileArg(path27, maxRows) {
9369
9371
  }
9370
9372
  }
9371
9373
  if (rows.length > maxRows) {
9372
- failWriteValidation2(`${path27} has ${rows.length} rows \u2014 the inline limit is ${maxRows}. Split the list.`);
9374
+ failWriteValidation2(`${path23} has ${rows.length} rows \u2014 the inline limit is ${maxRows}. Split the list.`);
9373
9375
  }
9374
9376
  return { columns, rows };
9375
9377
  }
@@ -11363,6 +11365,11 @@ var OPTIMIZATION_GOALS = [
11363
11365
  "CONVERSATIONS",
11364
11366
  "DERIVED_EVENTS"
11365
11367
  ];
11368
+ var OPTIMIZATION_GOALS_REQUIRING_PAGE = [
11369
+ "LEAD_GENERATION",
11370
+ "QUALITY_LEAD",
11371
+ "PAGE_LIKES"
11372
+ ];
11366
11373
  var DESTINATION_TYPES = [
11367
11374
  "WEBSITE",
11368
11375
  "APP",
@@ -11452,11 +11459,11 @@ var updateStatusSchema = z14.enum(UPDATE_STATUSES);
11452
11459
  function currencyMinimums2(currencyCode) {
11453
11460
  return CURRENCY_MINIMUMS2[currencyCode] ?? DEFAULT_CURRENCY_MINIMUM2;
11454
11461
  }
11455
- function validateDailyBudgetFloor(money, ctx, path27) {
11462
+ function validateDailyBudgetFloor(money, ctx, path23) {
11456
11463
  if (money?.currencyCode) {
11457
11464
  const min = currencyMinimums2(money.currencyCode).dailyBudgetMin;
11458
11465
  if (Number(money.amount) < min) {
11459
- ctx.addIssue({ code: "custom", path: path27, message: `below the ${min} ${money.currencyCode} daily minimum` });
11466
+ ctx.addIssue({ code: "custom", path: path23, message: `below the ${min} ${money.currencyCode} daily minimum` });
11460
11467
  }
11461
11468
  }
11462
11469
  }
@@ -11580,8 +11587,18 @@ function validateAdSetBudgetAndBid(p, ctx) {
11580
11587
  ctx.addIssue({ code: "custom", path: ["end_time"], message: "end_time must be after start_time" });
11581
11588
  }
11582
11589
  }
11590
+ function validateAdSetPromotedObject(p, ctx) {
11591
+ if (p.optimization_goal && OPTIMIZATION_GOALS_REQUIRING_PAGE.includes(p.optimization_goal) && !p.promoted_object?.page_id) {
11592
+ ctx.addIssue({
11593
+ code: "custom",
11594
+ path: ["promoted_object", "page_id"],
11595
+ message: `optimization_goal ${p.optimization_goal} needs promoted_object.page_id \u2014 pass --page-id with the Facebook Page the leads/likes belong to`
11596
+ });
11597
+ }
11598
+ }
11583
11599
  var adSetCreateSchema = z14.object(adSetFields).superRefine((p, ctx) => {
11584
11600
  validateAdSetBudgetAndBid(p, ctx);
11601
+ validateAdSetPromotedObject(p, ctx);
11585
11602
  });
11586
11603
  var adSetUpdateSchema = z14.object({
11587
11604
  name: adSetFields.name.optional(),
@@ -11602,6 +11619,9 @@ var adSetUpdateSchema = z14.object({
11602
11619
  ctx.addIssue({ code: "custom", message: "update needs at least one field" });
11603
11620
  }
11604
11621
  validateAdSetBudgetAndBid(p, ctx);
11622
+ if (p.optimization_goal) {
11623
+ validateAdSetPromotedObject(p, ctx);
11624
+ }
11605
11625
  });
11606
11626
  var messageSchema = z14.string().min(1).max(META_LIMITS.creative.messageHardMax);
11607
11627
  var headlineSchema2 = z14.string().min(1).max(META_LIMITS.creative.headlineMax);
@@ -11944,19 +11964,19 @@ function failWriteValidation3(message) {
11944
11964
  writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
11945
11965
  process.exit(1);
11946
11966
  }
11947
- function loadJsonFileArg3(path27) {
11948
- if (typeof path27 !== "string" || path27.length === 0) {
11967
+ function loadJsonFileArg3(path23) {
11968
+ if (typeof path23 !== "string" || path23.length === 0) {
11949
11969
  return {};
11950
11970
  }
11951
11971
  try {
11952
- const parsed = JSON.parse(readFileSync8(path27, "utf8"));
11972
+ const parsed = JSON.parse(readFileSync8(path23, "utf8"));
11953
11973
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
11954
- failWriteValidation3(`${path27} must contain a JSON object`);
11974
+ failWriteValidation3(`${path23} must contain a JSON object`);
11955
11975
  }
11956
11976
  return parsed;
11957
11977
  } catch (err) {
11958
11978
  if (err instanceof SyntaxError) {
11959
- failWriteValidation3(`${path27} is not valid JSON: ${err.message}`);
11979
+ failWriteValidation3(`${path23} is not valid JSON: ${err.message}`);
11960
11980
  }
11961
11981
  throw err;
11962
11982
  }
@@ -12189,7 +12209,10 @@ var adSetWriteArgs = {
12189
12209
  end: { type: "string", description: "End time" },
12190
12210
  "targeting-file": { type: "string", description: "JSON file with the full Meta targeting spec" },
12191
12211
  "promoted-object-file": { type: "string", description: "JSON file with the promoted_object" },
12192
- "page-id": { type: "string", description: "promoted_object.page_id shortcut" },
12212
+ "page-id": {
12213
+ type: "string",
12214
+ description: "promoted_object.page_id shortcut (required for LEAD_GENERATION / QUALITY_LEAD / PAGE_LIKES goals)"
12215
+ },
12193
12216
  "pixel-id": { type: "string", description: "promoted_object.pixel_id shortcut" },
12194
12217
  "custom-event-type": { type: "string", description: "promoted_object.custom_event_type (PURCHASE|LEAD|\u2026)" },
12195
12218
  file: { type: "string", description: "JSON file with the full payload; flags override" }
@@ -12199,6 +12222,7 @@ var adSetsCreateCommand = defineCommand54({
12199
12222
  name: "create",
12200
12223
  description: `Stage a new ad set. ${STAGED_NOTE2}
12201
12224
  Chain to a campaign with --campaign (real id or meta_temp_* ref).
12225
+ Lead-form goals (LEAD_GENERATION, QUALITY_LEAD) and PAGE_LIKES need --page-id (the Facebook Page the leads/likes belong to).
12202
12226
  Example: baker ads meta adsets create --campaign meta_temp_ab12 --name "US 25-54" --optimization-goal LINK_CLICKS --billing-event IMPRESSIONS --daily-budget 50 --targeting-file t.json`
12203
12227
  },
12204
12228
  args: { campaign: { type: "string", description: "Parent campaign id or meta_temp_* ref" }, ...adSetWriteArgs },
@@ -12212,6 +12236,7 @@ var adSetsUpdateCommand = defineCommand54({
12212
12236
  meta: {
12213
12237
  name: "update",
12214
12238
  description: `Stage changes to an ad set. ${STAGED_NOTE2}
12239
+ Switching --optimization-goal to a lead-form goal (LEAD_GENERATION, QUALITY_LEAD) or PAGE_LIKES needs --page-id in the same change.
12215
12240
  Example: baker ads meta adsets update 6123 --daily-budget 80`
12216
12241
  },
12217
12242
  args: { id: { type: "positional", description: "Ad set id or meta_temp_* ref", required: true }, ...adSetWriteArgs },
@@ -16123,13 +16148,13 @@ function referenceRank(type) {
16123
16148
  function orderSlotsByIdentity(slots) {
16124
16149
  return [...slots].sort((a, b) => referenceRank(a.type) - referenceRank(b.type));
16125
16150
  }
16126
- function buildFramePrompt(edge, sceneIndex, framePrompt, present2, hasAnchor, mode, sceneStyle, imageModel) {
16151
+ function buildFramePrompt(edge, sceneIndex, framePrompt, present, hasAnchor, mode, sceneStyle, imageModel) {
16127
16152
  const profile = imageModel ? imageProfileFor(imageModel) : void 0;
16128
16153
  const exclusionsLast = profile?.constraintPlacement === "last";
16129
16154
  const photoreal = profile ? profile.photorealCue : true;
16130
16155
  const EDGE = edge.toUpperCase();
16131
16156
  const legend = [
16132
- ...present2.map((s) => `- ${s.label} \u2014 ${roleForSlot(s)}`),
16157
+ ...present.map((s) => `- ${s.label} \u2014 ${roleForSlot(s)}`),
16133
16158
  ...hasAnchor ? [
16134
16159
  "- ORIGINAL_FRAME \u2014 use ONLY for composition: framing, camera angle, shot size, subject placement, pose, and proportions. IGNORE its text, logo, brand name, colors, AND the identity of every person/animal/object in it \u2014 those come from the labeled reference images above, never from this frame. It is a DIFFERENT brand's footage with DIFFERENT actors, here ONLY to anchor where things sit and how the shot is framed (e.g. a profile/side angle stays a profile/side angle), never who they are or what palette to use."
16135
16160
  ] : []
@@ -16187,7 +16212,7 @@ function buildFramePrompt(edge, sceneIndex, framePrompt, present2, hasAnchor, mo
16187
16212
  // RECAST is the whole point of a transform: the dropped el_* images define who/
16188
16213
  // what is on screen, NOT the source footage and NOT the prose. Without this, the
16189
16214
  // model reproduces the original ad's people (a proven failure mode).
16190
- ...present2.length > 0 ? [
16215
+ ...present.length > 0 ? [
16191
16216
  "IDENTITY & AESTHETIC \u2014 RECAST (this is a transform, not a copy):",
16192
16217
  "Identity comes from the reference image, never from the source footage or this prose. Render every",
16193
16218
  "person, animal, product, and set to MATCH its labeled reference image above \u2014 that image is the ONLY",
@@ -16242,17 +16267,17 @@ function ingestFrameRef(url, edge, ctx, nodes) {
16242
16267
  ctx.ingestCache?.set(url, ref);
16243
16268
  return ref;
16244
16269
  }
16245
- function buildFrameRef(edge, url, framePrompt, present2, ctx, nodes) {
16270
+ function buildFrameRef(edge, url, framePrompt, present, ctx, nodes) {
16246
16271
  const tag = ctx.tag ?? "";
16247
16272
  if (ctx.reuse && url) return ingestFrameRef(url, edge, ctx, nodes);
16248
- const castSlots = present2.filter((s) => {
16273
+ const castSlots = present.filter((s) => {
16249
16274
  const t = s.type.toLowerCase();
16250
16275
  return t === "person" || t === "animal";
16251
16276
  });
16252
16277
  const useOriginalAnchor = Boolean(url) && castSlots.length === 0;
16253
16278
  const hasOriginal = useOriginalAnchor;
16254
16279
  const originalRef = useOriginalAnchor && url ? ingestFrameRef(url, edge, ctx, nodes) : void 0;
16255
- const presentOrdered = orderSlotsByIdentity(present2);
16280
+ const presentOrdered = orderSlotsByIdentity(present);
16256
16281
  const reference = [...presentOrdered.map((s) => s.ref), ...originalRef ? [originalRef] : []];
16257
16282
  const imageProfile = imageProfileFor(ctx.imageModel);
16258
16283
  const genParams = {
@@ -16388,13 +16413,13 @@ function spokenTextParts(scene, nativeLine, loc, profile) {
16388
16413
  if (transcript) parts.push(`Transcript: ${loc(transcript)}`);
16389
16414
  return parts;
16390
16415
  }
16391
- function buildSeedancePrompt(scene, sceneIndex, present2, mode, audio, nativeLine, nativeLang, uiRouted, videoModel) {
16416
+ function buildSeedancePrompt(scene, sceneIndex, present, mode, audio, nativeLine, nativeLang, uiRouted, videoModel) {
16392
16417
  const profile = (videoModel ? clipProfileFor(videoModel) : void 0) ?? SEEDANCE_PROFILE;
16393
16418
  const loc = (s) => nativeLine ? localizeNumeralsForNative(s, nativeLang) : s;
16394
16419
  const routed = uiRouted ?? (compositeRegionsOf(scene) !== null && isUiOnlyComposite(compositeRegionsOf(scene) ?? []));
16395
16420
  const plate = routed ? compositePlateRegion(scene) ?? {} : null;
16396
16421
  const parts = visualBriefParts(scene, sceneIndex, plate);
16397
- const refs = plate ? present2.filter((s) => !UI_SURFACE_RE.test(s.description ?? "")) : present2;
16422
+ const refs = plate ? present.filter((s) => !UI_SURFACE_RE.test(s.description ?? "")) : present;
16398
16423
  if (refs.length > 0) {
16399
16424
  parts.push(
16400
16425
  `Keep these consistent with their references: ${refs.map((s) => `${s.label} (${s.description ?? s.type})`).join("; ")}`
@@ -16433,15 +16458,15 @@ function sceneOutTransition(scene, isLast) {
16433
16458
  const xfade = type ? XFADE_BY_TYPE[type] : void 0;
16434
16459
  return xfade ? { xfade, dur: TRANSITION_DEFAULT_S } : null;
16435
16460
  }
16436
- function sceneShootMode(scene, present2, nativeTurn, cameraOn, casts) {
16461
+ function sceneShootMode(scene, present, nativeTurn, cameraOn, casts) {
16437
16462
  const talking = Boolean(nativeTurn) || cameraOn && (scene.dialogue ?? []).some(
16438
16463
  (d) => d.line?.trim() && isOnCameraSpeaker(d.speaker ?? "voiceover", casts, cameraOn)
16439
16464
  );
16440
16465
  return deriveShootMode({
16441
16466
  explicit: scene.shoot_mode,
16442
16467
  talking,
16443
- hasPerson: present2.some((s) => s.type.toLowerCase() === "person" || s.type.toLowerCase() === "animal"),
16444
- hasProduct: present2.some((s) => s.type.toLowerCase() === "product")
16468
+ hasPerson: present.some((s) => s.type.toLowerCase() === "person" || s.type.toLowerCase() === "animal"),
16469
+ hasProduct: present.some((s) => s.type.toLowerCase() === "product")
16445
16470
  });
16446
16471
  }
16447
16472
  function emitSceneNativeAudio(i, nativeTurn, ambientBroll, lengths, clock, nodes, voTracks, nativeSegments, clipRef) {
@@ -16485,12 +16510,12 @@ function sameSpeakerTrackEntries(segments) {
16485
16510
  return sorted.map((s, k) => {
16486
16511
  const next = sorted[k + 1];
16487
16512
  const naturalLen = s.end_s - s.start_s;
16488
- const cap3 = next ? Math.min(s.end_s, next.start_s) - s.start_s : naturalLen;
16489
- const capped = cap3 < naturalLen - 1e-3;
16513
+ const cap2 = next ? Math.min(s.end_s, next.start_s) - s.start_s : naturalLen;
16514
+ const capped = cap2 < naturalLen - 1e-3;
16490
16515
  return {
16491
16516
  slot: `seg${k}`,
16492
16517
  start_s: s.start_s,
16493
- ...capped ? { duration_s: Math.round(Math.max(0.1, cap3) * 1e3) / 1e3 } : {}
16518
+ ...capped ? { duration_s: Math.round(Math.max(0.1, cap2) * 1e3) / 1e3 } : {}
16494
16519
  };
16495
16520
  });
16496
16521
  }
@@ -16529,7 +16554,7 @@ function buildPerSpeakerVoiceConversion(segments, totalMs, nodes) {
16529
16554
  }
16530
16555
  return tracks;
16531
16556
  }
16532
- function emitSceneClip(i, scene, present2, mode, nativeTurn, ambientBroll, frames, lengths, out, opts, nodes, tag = "", defer = false) {
16557
+ function emitSceneClip(i, scene, present, mode, nativeTurn, ambientBroll, frames, lengths, out, opts, nodes, tag = "", defer = false) {
16533
16558
  const profile = clipProfileFor(opts.videoModel) ?? SEEDANCE_PROFILE;
16534
16559
  const clipParams = {
16535
16560
  model: opts.videoModel,
@@ -16539,7 +16564,7 @@ function emitSceneClip(i, scene, present2, mode, nativeTurn, ambientBroll, frame
16539
16564
  prompt: buildSeedancePrompt(
16540
16565
  scene,
16541
16566
  i,
16542
- present2,
16567
+ present,
16543
16568
  mode,
16544
16569
  Boolean(nativeTurn) || ambientBroll,
16545
16570
  nativeTurn?.text,
@@ -16638,9 +16663,9 @@ function uiRoutedRuns(input) {
16638
16663
  flushRun();
16639
16664
  return runs;
16640
16665
  }
16641
- function sceneIsFullScreenUi(scene, present2) {
16666
+ function sceneIsFullScreenUi(scene, present) {
16642
16667
  if (scene.narrative_role?.trim() === "cta") return false;
16643
- const hasCast = present2.some((s) => {
16668
+ const hasCast = present.some((s) => {
16644
16669
  const t = s.type.toLowerCase();
16645
16670
  return t === "person" || t === "animal";
16646
16671
  });
@@ -16675,14 +16700,14 @@ function presenterIndexOf(regions, hasNative) {
16675
16700
  if (flagged >= 0) return flagged;
16676
16701
  return hasNative ? 0 : -1;
16677
16702
  }
16678
- function slotsForRegion(present2, isPresenter) {
16679
- return present2.filter((s) => {
16703
+ function slotsForRegion(present, isPresenter) {
16704
+ return present.filter((s) => {
16680
16705
  const t = s.type.toLowerCase();
16681
16706
  const person = t === "person" || t === "animal";
16682
16707
  return isPresenter ? person : !person;
16683
16708
  });
16684
16709
  }
16685
- function buildCompositeScene(layout, regions, comp, scene, i, present2, mode, nativeTurn, lengths, out, opts, nodes) {
16710
+ function buildCompositeScene(layout, regions, comp, scene, i, present, mode, nativeTurn, lengths, out, opts, nodes) {
16686
16711
  const dims = canvasDims(opts.outAr);
16687
16712
  const presIdx = presenterIndexOf(regions, Boolean(nativeTurn));
16688
16713
  const regionRefs = [];
@@ -16690,7 +16715,7 @@ function buildCompositeScene(layout, regions, comp, scene, i, present2, mode, na
16690
16715
  regions.forEach((region, r) => {
16691
16716
  const isPresenter = r === presIdx;
16692
16717
  const tag = `_r${r}`;
16693
- const regionSlots = slotsForRegion(present2, isPresenter);
16718
+ const regionSlots = slotsForRegion(present, isPresenter);
16694
16719
  const ctx = {
16695
16720
  sceneIndex: i,
16696
16721
  genAr: opts.genAr,
@@ -16790,14 +16815,14 @@ function sceneTiming(scene, isLast, nativeTurn, durationSet = SEEDANCE_DURATIONS
16790
16815
  const genDur = ceilToDurations(durationSet, Math.max(trimTarget, speech));
16791
16816
  return { dur, out, trimTarget, genDur, speech };
16792
16817
  }
16793
- function emitCompositeScene(composite, scene, i, present2, mode, nativeTurn, lengths, out, opts, clock, nodes, voTracks, nativeSegments, clips) {
16818
+ function emitCompositeScene(composite, scene, i, present, mode, nativeTurn, lengths, out, opts, clock, nodes, voTracks, nativeSegments, clips) {
16794
16819
  const built = buildCompositeScene(
16795
16820
  composite.layout,
16796
16821
  composite.regions,
16797
16822
  composite.comp,
16798
16823
  scene,
16799
16824
  i,
16800
- present2,
16825
+ present,
16801
16826
  mode,
16802
16827
  nativeTurn,
16803
16828
  { dur: lengths.dur, trimTarget: lengths.trimTarget, genDur: lengths.genDur },
@@ -16902,9 +16927,9 @@ function emitGraphicScene(i, scene, lengths, out, surfaceIngests, nodes, clips)
16902
16927
  clips.push({ ref: `$ref:${refId}.asset`, scene_s: lengths.dur, out, still: { fit: "pad" } });
16903
16928
  }
16904
16929
  var BRAND_CARD_RE = /\b(?:solid|plain|flat|brand|logo|wordmark|end[- ]?card|cta card|title card|colou?r background|background colou?r)\b/i;
16905
- function sceneIsBrandCard(scene, present2, isCta) {
16930
+ function sceneIsBrandCard(scene, present, isCta) {
16906
16931
  if (!isCta) return false;
16907
- const hasCast = present2.some((s) => {
16932
+ const hasCast = present.some((s) => {
16908
16933
  const t = s.type.toLowerCase();
16909
16934
  return t === "person" || t === "animal";
16910
16935
  });
@@ -17240,7 +17265,7 @@ function emitPhraseClip(phrase, voiceNode, env, nodes, out) {
17240
17265
  const anchor = phrase.shownScenes[0];
17241
17266
  const anchorScene = env.blueprint.scenes[anchor];
17242
17267
  if (!anchorScene) return;
17243
- const present2 = slotsForScene(env.slots, anchor);
17268
+ const present = slotsForScene(env.slots, anchor);
17244
17269
  const nativeTurn = {
17245
17270
  sceneIndex: anchor,
17246
17271
  speaker: phrase.speaker,
@@ -17250,7 +17275,7 @@ function emitPhraseClip(phrase, voiceNode, env, nodes, out) {
17250
17275
  voiceNode,
17251
17276
  native: true
17252
17277
  };
17253
- const mode = sceneShootMode(anchorScene, present2, nativeTurn, env.cameraOn, env.casts);
17278
+ const mode = sceneShootMode(anchorScene, present, nativeTurn, env.cameraOn, env.casts);
17254
17279
  const ctx = {
17255
17280
  sceneIndex: anchor,
17256
17281
  genAr: env.genAr,
@@ -17293,7 +17318,7 @@ function emitPhraseClip(phrase, voiceNode, env, nodes, out) {
17293
17318
  prompt: buildSeedancePrompt(
17294
17319
  anchorScene,
17295
17320
  anchor,
17296
- present2,
17321
+ present,
17297
17322
  mode,
17298
17323
  true,
17299
17324
  phrase.text,
@@ -17410,7 +17435,7 @@ function emitPhraseTts(phrase, voiceNode, idx, used, clock, nodes, out, language
17410
17435
  });
17411
17436
  }
17412
17437
  function emitCompositeInTimeline(composite, scene, i, isLast, env, canonical, ensureVoiceNode, usedVoIds, nodes, out) {
17413
- const present2 = slotsForScene(env.slots, i);
17438
+ const present = slotsForScene(env.slots, i);
17414
17439
  const onCam = (scene.dialogue ?? []).filter(
17415
17440
  (l) => Boolean(l.line?.trim()) && isOnCameraSpeaker(l.speaker ?? "voiceover", env.casts, env.cameraOn)
17416
17441
  );
@@ -17431,13 +17456,13 @@ function emitCompositeInTimeline(composite, scene, i, isLast, env, canonical, en
17431
17456
  speech_words: wordCount(text)
17432
17457
  });
17433
17458
  }
17434
- const mode = sceneShootMode(scene, present2, nativeTurn, env.cameraOn, env.casts);
17459
+ const mode = sceneShootMode(scene, present, nativeTurn, env.cameraOn, env.casts);
17435
17460
  const lengths = sceneTiming(scene, isLast, nativeTurn, clipProfileFor(env.opts.videoModel)?.durationSet);
17436
17461
  emitCompositeScene(
17437
17462
  composite,
17438
17463
  scene,
17439
17464
  i,
17440
- present2,
17465
+ present,
17441
17466
  mode,
17442
17467
  nativeTurn,
17443
17468
  lengths,
@@ -17532,8 +17557,8 @@ function brollKeyframes(scene, i, env, ctx, lengths, prevEndFrame, nodes) {
17532
17557
  return { first, last, sharesPrevFrame };
17533
17558
  }
17534
17559
  function emitBrollScene(scene, i, isLast, env, nodes, out, prevEndFrame) {
17535
- const present2 = slotsForScene(env.slots, i);
17536
- const mode = sceneShootMode(scene, present2, void 0, env.cameraOn, env.casts);
17560
+ const present = slotsForScene(env.slots, i);
17561
+ const mode = sceneShootMode(scene, present, void 0, env.cameraOn, env.casts);
17537
17562
  const ambientBroll = Boolean(env.opts.ambient) && mode !== "ugc_selfie";
17538
17563
  const lengths = sceneTiming(scene, isLast, void 0, clipProfileFor(env.opts.videoModel)?.durationSet);
17539
17564
  const ctx = {
@@ -17550,13 +17575,13 @@ function emitBrollScene(scene, i, isLast, env, nodes, out, prevEndFrame) {
17550
17575
  out.routes.set(i, "graphic_plate");
17551
17576
  return void 0;
17552
17577
  }
17553
- if (!env.reuse && sceneIsFullScreenUi(scene, present2)) {
17578
+ if (!env.reuse && sceneIsFullScreenUi(scene, present)) {
17554
17579
  emitScreenScene(i, scene, lengths, lengths.out, env.surfaceIngests, nodes, out.clips);
17555
17580
  out.routes.set(i, "screen_still");
17556
17581
  return void 0;
17557
17582
  }
17558
17583
  const isCta = scene.narrative_role?.trim() === "cta" || isLast;
17559
- if (!env.reuse && sceneIsBrandCard(scene, present2, isCta)) {
17584
+ if (!env.reuse && sceneIsBrandCard(scene, present, isCta)) {
17560
17585
  emitBrandCardScene(i, lengths, lengths.out, env.outAr, brandPlateColor(env.blueprint), nodes, out.clips);
17561
17586
  out.routes.set(i, "brand_card");
17562
17587
  return void 0;
@@ -17571,7 +17596,7 @@ function emitBrollScene(scene, i, isLast, env, nodes, out, prevEndFrame) {
17571
17596
  const clip = emitSceneClip(
17572
17597
  i,
17573
17598
  scene,
17574
- present2,
17599
+ present,
17575
17600
  mode,
17576
17601
  void 0,
17577
17602
  ambientBroll,
@@ -18333,7 +18358,7 @@ function sceneSpokenText(scene) {
18333
18358
  return (scene.dialogue ?? []).map((d) => d.line?.trim()).filter((l) => Boolean(l)).join(" ") || null;
18334
18359
  }
18335
18360
  function buildMotionBoard(blueprint) {
18336
- const round5 = (n) => Math.round(n * 100) / 100;
18361
+ const round4 = (n) => Math.round(n * 100) / 100;
18337
18362
  let cursor = 0;
18338
18363
  return blueprint.scenes.map((scene, i) => {
18339
18364
  const start_s = scene.start_s ?? cursor;
@@ -18345,15 +18370,15 @@ function buildMotionBoard(blueprint) {
18345
18370
  const graphics = [
18346
18371
  ...(overlays.success ? overlays.data : []).filter((ov) => ov.text?.trim()).map((ov) => ({
18347
18372
  kind: "text",
18348
- at_s: round5(ov.appears_at_s ?? start_s),
18349
- dur_s: round5(ov.duration_s ?? 2.5),
18373
+ at_s: round4(ov.appears_at_s ?? start_s),
18374
+ dur_s: round4(ov.duration_s ?? 2.5),
18350
18375
  position: ov.position ?? "bottom_center",
18351
18376
  text: ov.text?.trim()
18352
18377
  })),
18353
18378
  ...(floats.success ? floats.data : []).map((fe) => ({
18354
18379
  kind: "graphic",
18355
- at_s: round5(fe.appears_at_s ?? start_s),
18356
- dur_s: round5(fe.duration_s ?? 2.5),
18380
+ at_s: round4(fe.appears_at_s ?? start_s),
18381
+ dur_s: round4(fe.duration_s ?? 2.5),
18357
18382
  position: fe.position ?? "bottom_center",
18358
18383
  label: fe.brand_name || fe.what_it_represents || fe.description || fe.kind || "element"
18359
18384
  }))
@@ -18361,7 +18386,7 @@ function buildMotionBoard(blueprint) {
18361
18386
  return {
18362
18387
  scene: i,
18363
18388
  role: resolveSceneRole(scene, i, blueprint.scenes.length),
18364
- window_s: [round5(start_s), round5(end_s)],
18389
+ window_s: [round4(start_s), round4(end_s)],
18365
18390
  // A continuation b-roll scene shares the previous scene's end frame as its start
18366
18391
  // (no own `s<i>_start` node), so point the storyboard at that shared keyframe.
18367
18392
  storyboard_frames: [scene.continues_previous && i > 0 ? `s${i - 1}_end` : `s${i}_start`],
@@ -19026,10 +19051,10 @@ function runDirsToPrune(entries, keep, currentRunId) {
19026
19051
  return runs.slice(0, Math.max(0, runs.length - keep));
19027
19052
  }
19028
19053
  async function pruneOldRuns(outputsDir, keep, currentRunId, log) {
19029
- const { readdir: readdir8 } = await import("fs/promises");
19054
+ const { readdir: readdir7 } = await import("fs/promises");
19030
19055
  let entries;
19031
19056
  try {
19032
- entries = await readdir8(outputsDir);
19057
+ entries = await readdir7(outputsDir);
19033
19058
  } catch {
19034
19059
  return;
19035
19060
  }
@@ -20801,10 +20826,10 @@ function resolveShippedCanvasDir(name, startDir, exists = existsSync3, maxDepth
20801
20826
  import { appendFile, readFile as readFile13 } from "fs/promises";
20802
20827
  import path19 from "path";
20803
20828
  function missingGitignoreEntries(existing, entries) {
20804
- const present2 = new Set(
20829
+ const present = new Set(
20805
20830
  existing.split("\n").map((l) => l.trim().replace(/\/+$/, "")).filter((l) => l.length > 0 && !l.startsWith("#"))
20806
20831
  );
20807
- return entries.filter((e) => !present2.has(e.trim().replace(/\/+$/, "")));
20832
+ return entries.filter((e) => !present.has(e.trim().replace(/\/+$/, "")));
20808
20833
  }
20809
20834
  async function ensureGitignore(dir, entries) {
20810
20835
  const file = path19.join(dir, ".gitignore");
@@ -22076,12 +22101,12 @@ function listFlowSlugs() {
22076
22101
  return readdirSync2(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith("_") && entry.name !== ".gitkeep").map((entry) => entry.name).sort();
22077
22102
  }
22078
22103
  function readFlowTree(slug) {
22079
- const path27 = join3(flowsDir(), slug, "_data.json");
22080
- if (!existsSync4(path27)) {
22104
+ const path23 = join3(flowsDir(), slug, "_data.json");
22105
+ if (!existsSync4(path23)) {
22081
22106
  failLocal(`No form "${slug}". Run "baker flows list" to see the forms in this workspace.`);
22082
22107
  }
22083
22108
  try {
22084
- return JSON.parse(readFileSync9(path27, "utf-8"));
22109
+ return JSON.parse(readFileSync9(path23, "utf-8"));
22085
22110
  } catch (error) {
22086
22111
  failLocal(`Could not read form "${slug}": ${error instanceof Error ? error.message : String(error)}`);
22087
22112
  }
@@ -23237,9 +23262,9 @@ async function readImageBuffer(pathOrUrl) {
23237
23262
  }
23238
23263
  return readFile18(pathOrUrl);
23239
23264
  }
23240
- async function isDirectory(path27) {
23265
+ async function isDirectory(path23) {
23241
23266
  try {
23242
- const s = await stat4(path27);
23267
+ const s = await stat4(path23);
23243
23268
  return s.isDirectory();
23244
23269
  } catch {
23245
23270
  return false;
@@ -24939,7 +24964,7 @@ function emitError3(err) {
24939
24964
  process.exit(1);
24940
24965
  }
24941
24966
  function coerceRawArgs(args) {
24942
- const num2 = (v) => {
24967
+ const num = (v) => {
24943
24968
  if (typeof v === "number") return v;
24944
24969
  if (typeof v === "string" && v.length > 0) {
24945
24970
  const n = Number(v);
@@ -24954,8 +24979,8 @@ function coerceRawArgs(args) {
24954
24979
  color: str(args.color),
24955
24980
  "remove-bg": bool(args["remove-bg"]),
24956
24981
  "shrink-to-content": bool(args["shrink-to-content"]),
24957
- height: num2(args.height),
24958
- width: num2(args.width),
24982
+ height: num(args.height),
24983
+ width: num(args.width),
24959
24984
  size: str(args.size),
24960
24985
  fit: str(args.fit),
24961
24986
  output: str(args.output),
@@ -25802,1031 +25827,8 @@ Paid transforms (run on the Convex backend, cost-tracked):
25802
25827
  }
25803
25828
  });
25804
25829
 
25805
- // src/commands/landing/index.ts
25806
- import { defineCommand as defineCommand134 } from "citty";
25807
-
25808
- // src/commands/landing/critique.ts
25809
- import { stat as stat6 } from "fs/promises";
25810
- import path26 from "path";
25811
- import { defineCommand as defineCommand133 } from "citty";
25812
-
25813
- // src/engine/landing/lib/brand-tokens.ts
25814
- import { readFile as readFile20 } from "fs/promises";
25815
- import path23 from "path";
25816
-
25817
- // src/engine/landing/lib/color.ts
25818
- var NEUTRAL_COLOR_KEYWORDS = /* @__PURE__ */ new Set([
25819
- "transparent",
25820
- "currentcolor",
25821
- "black",
25822
- "white",
25823
- "gray",
25824
- "grey",
25825
- "silver",
25826
- "dimgray",
25827
- "dimgrey",
25828
- "darkgray",
25829
- "darkgrey",
25830
- "lightgray",
25831
- "lightgrey",
25832
- "gainsboro",
25833
- "whitesmoke"
25834
- ]);
25835
- function hexChannels(color) {
25836
- const long = color.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})(?:[0-9a-f]{2})?$/i);
25837
- if (long)
25838
- return [
25839
- Number.parseInt(long[1] ?? "0", 16),
25840
- Number.parseInt(long[2] ?? "0", 16),
25841
- Number.parseInt(long[3] ?? "0", 16)
25842
- ];
25843
- const short = color.match(/^#([0-9a-f])([0-9a-f])([0-9a-f])(?:[0-9a-f])?$/i);
25844
- if (short) {
25845
- const dup = (s) => Number.parseInt(`${s ?? "0"}${s ?? "0"}`, 16);
25846
- return [dup(short[1]), dup(short[2]), dup(short[3])];
25847
- }
25848
- return null;
25849
- }
25850
- function parseColor2(raw) {
25851
- const c = String(raw || "").trim().toLowerCase();
25852
- if (!c || c === "transparent") return null;
25853
- const rgb = c.match(/rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)(?:[\s,/]+([\d.]+%?))?\s*\)/i);
25854
- if (rgb) {
25855
- const alpha = rgb[4];
25856
- const a = alpha === void 0 ? 1 : alpha.endsWith("%") ? Number.parseFloat(alpha) / 100 : Number.parseFloat(alpha);
25857
- return { r: Number(rgb[1]), g: Number(rgb[2]), b: Number(rgb[3]), a };
25858
- }
25859
- const hex = hexChannels(c);
25860
- if (hex) return { r: hex[0], g: hex[1], b: hex[2], a: 1 };
25861
- return null;
25862
- }
25863
- function isNeutralAuthoredColor(rawColor) {
25864
- const c = String(rawColor || "").trim().toLowerCase();
25865
- if (!c) return false;
25866
- if (NEUTRAL_COLOR_KEYWORDS.has(c)) return true;
25867
- if (/^rgba?\(/i.test(c)) {
25868
- const channels2 = c.match(/^rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)/i);
25869
- if (channels2) {
25870
- const values = [1, 2, 3].map((i) => Number(channels2[i]));
25871
- return Math.max(...values) - Math.min(...values) < 30;
25872
- }
25873
- return false;
25874
- }
25875
- const oklch = c.match(/oklch\(\s*[\d.]+%?\s+([\d.-]+)/i);
25876
- if (oklch) return Number.parseFloat(oklch[1] ?? "0") < 0.02;
25877
- const lch = c.match(/lch\(\s*[\d.]+%?\s+([\d.-]+)/i);
25878
- if (lch) return Number.parseFloat(lch[1] ?? "0") < 3;
25879
- const hsl = c.match(/hsla?\(\s*[\d.-]+\s*,?\s*([\d.]+)%/i);
25880
- if (hsl) return Number.parseFloat(hsl[1] ?? "0") < 10;
25881
- const channels = hexChannels(c);
25882
- if (channels) return Math.max(...channels) - Math.min(...channels) < 30;
25883
- return false;
25884
- }
25885
- function hasChroma(c, threshold = 30) {
25886
- if (!c) return false;
25887
- return Math.max(c.r, c.g, c.b) - Math.min(c.r, c.g, c.b) >= threshold;
25888
- }
25889
- function getHue(c) {
25890
- if (!c) return 0;
25891
- const r = c.r / 255;
25892
- const g = c.g / 255;
25893
- const b = c.b / 255;
25894
- const max = Math.max(r, g, b);
25895
- const min = Math.min(r, g, b);
25896
- if (max === min) return 0;
25897
- const d = max - min;
25898
- let h;
25899
- if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
25900
- else if (max === g) h = ((b - r) / d + 2) / 6;
25901
- else h = ((r - g) / d + 4) / 6;
25902
- return Math.round(h * 360);
25903
- }
25904
- function isAiPurpleHue(hue) {
25905
- return hue >= 260 && hue <= 310;
25906
- }
25907
- function isCreamColor(c) {
25908
- if (Math.min(c.r, c.g, c.b) < 209) return false;
25909
- if (!(c.r >= c.g && c.g >= c.b)) return false;
25910
- const warmth = c.r - c.b;
25911
- return warmth >= 6 && warmth <= 48;
25912
- }
25913
-
25914
- // src/engine/landing/lib/brand-tokens.ts
25915
- var EMPTY = { fonts: /* @__PURE__ */ new Set(), colors: [], hasTokens: false };
25916
- function parseBrandTokens(globalCss, brandMd) {
25917
- const fonts = /* @__PURE__ */ new Set();
25918
- const colors = [];
25919
- parseGlobalCss(globalCss, fonts, colors);
25920
- parseBrandMd(brandMd, fonts, colors);
25921
- return { fonts, colors, hasTokens: fonts.size > 0 || colors.length > 0 };
25922
- }
25923
- function parseGlobalCss(globalCss, fonts, colors) {
25924
- for (const m of globalCss.matchAll(/--font-[\w-]*\s*:\s*([^;]+);/gi)) {
25925
- const first = (m[1] ?? "").split(",")[0]?.trim().replace(/^['"]|['"]$/g, "").toLowerCase();
25926
- if (first) fonts.add(first);
25927
- }
25928
- for (const m of globalCss.matchAll(
25929
- /--color-[\w-]*\s*:\s*(#[0-9a-f]{3,8}\b|rgba?\([^)]+\)|oklch\([^)]+\)|hsla?\([^)]+\))/gi
25930
- )) {
25931
- const c = parseColor2(m[1] ?? "");
25932
- if (c) colors.push(c);
25933
- }
25934
- }
25935
- function parseBrandMd(brandMd, fonts, colors) {
25936
- for (const m of brandMd.matchAll(/#[0-9a-f]{6}\b/gi)) {
25937
- const c = parseColor2(m[0]);
25938
- if (c) colors.push(c);
25939
- }
25940
- for (const line of brandMd.split("\n")) {
25941
- if (!/\b(?:font|typeface|family)\b/i.test(line)) continue;
25942
- const named = line.match(/["'`]([A-Za-z][\w ]{1,30})["'`]/g);
25943
- if (named) for (const n of named) fonts.add(n.slice(1, -1).trim().toLowerCase());
25944
- }
25945
- }
25946
- async function loadBrandTokens(projectRoot) {
25947
- const globalCss = await safeRead(path23.join(projectRoot, "src", "styles", "global.css"));
25948
- const brandMd = await safeRead(path23.join(projectRoot, "src", "brand", "BRAND.md"));
25949
- if (!globalCss && !brandMd) return EMPTY;
25950
- return parseBrandTokens(globalCss, brandMd);
25951
- }
25952
- async function safeRead(file) {
25953
- try {
25954
- return await readFile20(file, "utf8");
25955
- } catch {
25956
- return "";
25957
- }
25958
- }
25959
- function brandCommitsFont(brand, snippet) {
25960
- if (brand.fonts.size === 0) return false;
25961
- const lower = snippet.toLowerCase();
25962
- for (const f of brand.fonts) if (f.length >= 3 && lower.includes(f)) return true;
25963
- return false;
25964
- }
25965
- function brandCommitsAiHue(brand) {
25966
- return brand.colors.some((c) => hasChroma(c, 40) && isAiPurpleHue(getHue(c)));
25967
- }
25968
- function brandCommitsCream(brand) {
25969
- return brand.colors.some((c) => isCreamColor(c));
25970
- }
25971
-
25972
- // src/engine/landing/lib/constants.ts
25973
- var OVERUSED_FONTS = /* @__PURE__ */ new Set([
25974
- // Older monoculture (still ubiquitous):
25975
- "inter",
25976
- "roboto",
25977
- "open sans",
25978
- "lato",
25979
- "montserrat",
25980
- "arial",
25981
- "helvetica",
25982
- // Newer monoculture (the Anthropic-skill / Vercel / GitHub default wave):
25983
- "fraunces",
25984
- "instrument sans",
25985
- "instrument serif",
25986
- "geist",
25987
- "geist sans",
25988
- "geist mono",
25989
- "mona sans",
25990
- "plus jakarta sans",
25991
- "space grotesk",
25992
- "space mono",
25993
- "recoleta",
25994
- "dm sans",
25995
- "dm serif display",
25996
- "dm serif text",
25997
- "outfit",
25998
- "syne",
25999
- "cormorant",
26000
- "playfair display",
26001
- "lora",
26002
- "crimson",
26003
- "newsreader",
26004
- "ibm plex",
26005
- "ibm plex sans",
26006
- "ibm plex serif"
26007
- ]);
26008
- var GENERIC_FONTS = /* @__PURE__ */ new Set([
26009
- "serif",
26010
- "sans-serif",
26011
- "monospace",
26012
- "cursive",
26013
- "fantasy",
26014
- "system-ui",
26015
- "ui-serif",
26016
- "ui-sans-serif",
26017
- "ui-monospace",
26018
- "ui-rounded",
26019
- "-apple-system",
26020
- "blinkmacsystemfont",
26021
- "segoe ui",
26022
- "inherit",
26023
- "initial",
26024
- "unset",
26025
- "revert"
26026
- ]);
26027
- var EM_DASH_FLOOR = 8;
26028
- var EM_DASH_CHARS_PER_DASH = 500;
26029
- var TAILWIND_TEXT_PX = {
26030
- "text-xs": 12,
26031
- "text-sm": 14,
26032
- "text-base": 16,
26033
- "text-lg": 18,
26034
- "text-xl": 20,
26035
- "text-2xl": 24,
26036
- "text-3xl": 30,
26037
- "text-4xl": 36,
26038
- "text-5xl": 48,
26039
- "text-6xl": 60,
26040
- "text-7xl": 72,
26041
- "text-8xl": 96,
26042
- "text-9xl": 128
26043
- };
26044
- var BUZZWORDS = [
26045
- "streamline your",
26046
- "empower your",
26047
- "supercharge your",
26048
- "unleash your",
26049
- "unleash the power",
26050
- "leverage the power",
26051
- "built for the modern",
26052
- "trusted by leading",
26053
- "trusted by the world",
26054
- "best-in-class",
26055
- "industry-leading",
26056
- "world-class",
26057
- "enterprise-grade",
26058
- "next-generation",
26059
- "cutting-edge",
26060
- "transform your business",
26061
- "revolutionize",
26062
- "game-changer",
26063
- "game changing",
26064
- "mission-critical",
26065
- "best of breed",
26066
- "future-proof",
26067
- "future proof",
26068
- "seamless experience",
26069
- "seamlessly integrate",
26070
- "drive engagement",
26071
- "drive growth",
26072
- "drive results",
26073
- "harness the power"
26074
- ];
26075
- var AI_PURPLE_HEXES = /* @__PURE__ */ new Set([
26076
- "#7c3aed",
26077
- "#8b5cf6",
26078
- "#a855f7",
26079
- "#9333ea",
26080
- "#7e22ce",
26081
- "#6d28d9",
26082
- "#6366f1",
26083
- "#764ba2",
26084
- "#667eea"
26085
- ]);
26086
- var CREAM_TAILWIND_BG = /* @__PURE__ */ new Set([
26087
- "bg-amber-50",
26088
- "bg-amber-100",
26089
- "bg-orange-50",
26090
- "bg-orange-100",
26091
- "bg-yellow-50",
26092
- "bg-stone-50",
26093
- "bg-stone-100",
26094
- "bg-stone-200"
26095
- ]);
26096
- var RULE_META = {
26097
- "gradient-text": {
26098
- family: "color",
26099
- severity: "block",
26100
- note: "Gradient text is a top AI tell. Emphasis comes from weight or size, not a clipped gradient fill."
26101
- },
26102
- "broken-image": {
26103
- family: "integrity",
26104
- severity: "block",
26105
- note: "An <img> with an empty or placeholder src ships a broken image. Source a real asset via `baker images`."
26106
- },
26107
- "side-tab": {
26108
- family: "borders_depth",
26109
- severity: "warn",
26110
- note: "A thick colored border on one edge is the 'side-tab' AI tell. Use full borders, a background, or spacing instead."
26111
- },
26112
- "border-accent-on-rounded": {
26113
- family: "borders_depth",
26114
- severity: "warn",
26115
- note: "A colored top/bottom accent bar on a rounded card is an AI tell. Let the card's own surface carry the accent."
26116
- },
26117
- "overused-font": {
26118
- family: "typography",
26119
- severity: "warn",
26120
- brandAware: "font",
26121
- note: "This face is a training-data default. Pick a face with a point of view \u2014 unless BRAND.md commits it."
26122
- },
26123
- "single-font": {
26124
- family: "typography",
26125
- severity: "advisory",
26126
- note: "Only one face across the page flattens hierarchy. A display/body pair usually reads richer."
26127
- },
26128
- "flat-type-hierarchy": {
26129
- family: "typography",
26130
- severity: "warn",
26131
- note: "Type steps are too close (<2\xD7 span). Widen the scale so the primary, secondary, and body are obvious at a glance."
26132
- },
26133
- "ai-color-palette": {
26134
- family: "color",
26135
- severity: "warn",
26136
- brandAware: "color",
26137
- note: "Purple/indigo-on-heading or purple\u2192cyan gradients are the AI palette cluster. Commit to the brand's own hues."
26138
- },
26139
- "cream-palette": {
26140
- family: "color",
26141
- severity: "warn",
26142
- brandAware: "color",
26143
- note: "Cream/parchment ground is the 'measured rendition' AI default. Rework from the brand's saturated materials."
26144
- },
26145
- "gray-on-color": {
26146
- family: "color",
26147
- severity: "warn",
26148
- note: "Gray text on a colored surface reads muddy. Tint secondary text from the surface hue, never neutral gray."
26149
- },
26150
- "bounce-easing": {
26151
- family: "motion",
26152
- severity: "warn",
26153
- note: "Bounce/elastic easing feels dated. Use natural deceleration (e.g. cubic-bezier(0.16, 1, 0.3, 1))."
26154
- },
26155
- "layout-transition": {
26156
- family: "motion",
26157
- severity: "advisory",
26158
- note: "Transitioning width/height/padding/margin janks. Animate transform/opacity, or clip-path/max-height with care."
26159
- },
26160
- "monotonous-spacing": {
26161
- family: "spacing",
26162
- severity: "warn",
26163
- note: "One spacing value dominates. Create rhythm \u2014 tight groups, generous separation, more space above a heading than below."
26164
- },
26165
- "marketing-buzzword": {
26166
- family: "copy",
26167
- severity: "warn",
26168
- note: "SaaS filler ('supercharge your\u2026') is generic. Name names, use numbers, say the specific thing only this product proves."
26169
- },
26170
- "aphoristic-cadence": {
26171
- family: "copy",
26172
- severity: "warn",
26173
- note: "Manufactured-contrast cadence ('Not an X. A Y.') is an AI copy tell. Vary sentence shape; make a real claim."
26174
- },
26175
- "em-dash-overuse": {
26176
- family: "copy",
26177
- severity: "warn",
26178
- note: "Dense em-dashes are an AI cadence tell (and brand copy bans them). Recast with commas, colons, or full stops."
26179
- },
26180
- "dark-glow": {
26181
- family: "borders_depth",
26182
- severity: "warn",
26183
- note: "A zero-offset colored glow is decoration, not depth. Shadows carry an offset and a soft blur."
26184
- },
26185
- marquee: {
26186
- family: "motion",
26187
- severity: "warn",
26188
- note: "Auto-scrolling marquees are a dated tell. Let the visitor control the pace, or drop the motion."
26189
- }
26190
- };
26191
- var SEVERITY_WEIGHT = {
26192
- block: 0.5,
26193
- warn: 0.2,
26194
- advisory: 0.05
26195
- };
26196
-
26197
- // src/engine/landing/lib/rules.ts
26198
- var cap2 = (m, i) => m[i] ?? "";
26199
- var num = (m, i) => Number(m[i] ?? 0);
26200
- var hasRounded = (line) => /\brounded(?:-\w+)?\b/.test(line) || /border-radius/i.test(line);
26201
- var isSafeElement = (line) => /<(?:blockquote|nav[\s>]|pre[\s>]|code[\s>]|a\s|input[\s>]|span[\s>])/i.test(line);
26202
- function isNeutralBorderColor(str) {
26203
- const m = str.match(/solid\s+((?:rgba?|hsla?|oklch|oklab|lab|lch|hwb|color)\([^)]*\)|#[0-9a-f]{3,8}\b|[a-z]+)/i);
26204
- if (!m) return false;
26205
- return isNeutralAuthoredColor(m[1] ?? "");
26206
- }
26207
- var LINE_MATCHERS = [
26208
- // ── Side-tab: a thick colored border on one edge ──────────────────────────
26209
- {
26210
- id: "side-tab",
26211
- regex: /\bborder-[lrse]-(\d+)\b/g,
26212
- test: (m, line) => hasRounded(line) ? num(m, 1) >= 2 : num(m, 1) >= 4,
26213
- fmt: (m) => cap2(m, 0)
26214
- },
26215
- {
26216
- id: "side-tab",
26217
- regex: /border-(?:left|right)\s*:\s*(\d+)px\s+solid[^;]*/gi,
26218
- test: (m, line) => {
26219
- if (isSafeElement(line)) return false;
26220
- if (isNeutralBorderColor(cap2(m, 0))) return false;
26221
- return hasRounded(line) ? num(m, 1) >= 2 : num(m, 1) >= 3;
26222
- },
26223
- fmt: (m) => cap2(m, 0).replace(/\s*;?\s*$/, "")
26224
- },
26225
- {
26226
- id: "side-tab",
26227
- regex: /border-(?:left|right)-width\s*:\s*(\d+)px/gi,
26228
- test: (m, line) => !isSafeElement(line) && num(m, 1) >= 3,
26229
- fmt: (m) => cap2(m, 0)
26230
- },
26231
- {
26232
- id: "side-tab",
26233
- regex: /border-inline-(?:start|end)(?:-width)?\s*:\s*(\d+)px/gi,
26234
- test: (m, line) => !isSafeElement(line) && num(m, 1) >= 3,
26235
- fmt: (m) => cap2(m, 0)
26236
- },
26237
- // ── Border accent on a rounded card (top/bottom colored bar) ──────────────
26238
- {
26239
- id: "border-accent-on-rounded",
26240
- regex: /\bborder-[tb]-(\d+)\b/g,
26241
- test: (m, line) => hasRounded(line) && num(m, 1) >= 1,
26242
- fmt: (m) => cap2(m, 0)
26243
- },
26244
- {
26245
- id: "border-accent-on-rounded",
26246
- regex: /border-(?:top|bottom)\s*:\s*(\d+)px\s+solid/gi,
26247
- test: (m, line) => num(m, 1) >= 3 && hasRounded(line),
26248
- fmt: (m) => cap2(m, 0)
26249
- },
26250
- // ── Gradient text ─────────────────────────────────────────────────────────
26251
- {
26252
- id: "gradient-text",
26253
- regex: /background-clip\s*:\s*text|-webkit-background-clip\s*:\s*text/gi,
26254
- test: (_m, line) => /gradient/i.test(line),
26255
- fmt: () => "background-clip: text + gradient"
26256
- },
26257
- {
26258
- id: "gradient-text",
26259
- regex: /\bbg-clip-text\b/g,
26260
- test: (_m, line) => /\bbg-gradient-to-|\bbg-\[linear-gradient/i.test(line),
26261
- fmt: () => "bg-clip-text + gradient"
26262
- },
26263
- // ── Gray text on a colored background (Tailwind) ──────────────────────────
26264
- {
26265
- id: "gray-on-color",
26266
- regex: /\btext-(?:gray|slate|zinc|neutral|stone)-(\d+)\b/g,
26267
- test: (_m, line) => /\bbg-(?:red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-\d+\b/.test(
26268
- line
26269
- ),
26270
- fmt: (m, line) => {
26271
- const bg = line.match(
26272
- /\bbg-(?:red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-\d+\b/
26273
- );
26274
- return `${cap2(m, 0)} on ${bg?.[0] || "?"}`;
26275
- }
26276
- },
26277
- // ── AI color palette (Tailwind purple/indigo + banned hero hexes) ─────────
26278
- {
26279
- id: "ai-color-palette",
26280
- regex: /\btext-(?:purple|violet|indigo)-(\d+)\b/g,
26281
- test: (_m, line) => /\btext-(?:[2-9]xl)\b|<h[1-3]/i.test(line),
26282
- fmt: (m) => `${cap2(m, 0)} on heading`
26283
- },
26284
- {
26285
- id: "ai-color-palette",
26286
- regex: /\bfrom-(?:purple|violet|indigo)-(\d+)\b/g,
26287
- test: (_m, line) => /\bto-(?:purple|violet|indigo|blue|cyan|pink|fuchsia)-\d+\b/.test(line),
26288
- fmt: (m) => `${cap2(m, 0)} gradient`
26289
- },
26290
- {
26291
- id: "ai-color-palette",
26292
- regex: /#[0-9a-f]{6}\b/gi,
26293
- test: (m) => AI_PURPLE_HEXES.has(cap2(m, 0).toLowerCase()),
26294
- fmt: (m) => `${cap2(m, 0)} (AI-cluster purple)`
26295
- },
26296
- // ── Cream/parchment ground (Tailwind + authored background hex) ───────────
26297
- {
26298
- id: "cream-palette",
26299
- regex: /\bbg-(?:amber|orange|yellow|stone)-(?:50|100|200)\b/g,
26300
- test: (m) => CREAM_TAILWIND_BG.has(cap2(m, 0)),
26301
- fmt: (m) => `${cap2(m, 0)} (cream ground)`
26302
- },
26303
- {
26304
- id: "cream-palette",
26305
- regex: /background(?:-color)?\s*:\s*(#[0-9a-f]{6}\b|rgba?\([^)]+\))/gi,
26306
- test: (m) => {
26307
- const c = parseColor2(cap2(m, 1));
26308
- return c !== null && isCreamColor(c);
26309
- },
26310
- fmt: (m) => `${cap2(m, 1)} (cream ground)`
26311
- },
26312
- // ── Bounce / elastic easing ───────────────────────────────────────────────
26313
- {
26314
- id: "bounce-easing",
26315
- regex: /\banimate-bounce\b/g,
26316
- test: () => true,
26317
- fmt: () => "animate-bounce (Tailwind)"
26318
- },
26319
- {
26320
- id: "bounce-easing",
26321
- regex: /animation(?:-name)?\s*:\s*([^;{}]*(?:bounce|elastic|wobble|jiggle|spring)[^;{}]*)/gi,
26322
- test: () => true,
26323
- fmt: (m) => `animation: ${cap2(m, 1).trim()}`
26324
- },
26325
- {
26326
- id: "bounce-easing",
26327
- regex: /cubic-bezier\(\s*([\d.-]+)\s*,\s*([\d.-]+)\s*,\s*([\d.-]+)\s*,\s*([\d.-]+)\s*\)/g,
26328
- test: (m) => {
26329
- const y1 = Number.parseFloat(cap2(m, 2));
26330
- const y2 = Number.parseFloat(cap2(m, 4));
26331
- return y1 < -0.1 || y1 > 1.1 || y2 < -0.1 || y2 > 1.1;
26332
- },
26333
- fmt: (m) => `cubic-bezier(${cap2(m, 1)}, ${cap2(m, 2)}, ${cap2(m, 3)}, ${cap2(m, 4)})`
26334
- },
26335
- // ── Transitioning layout properties (jank) ────────────────────────────────
26336
- {
26337
- id: "layout-transition",
26338
- regex: /transition(?:-property)?\s*:\s*([^;{}]+)/gi,
26339
- test: (m) => {
26340
- const val = cap2(m, 1).toLowerCase();
26341
- if (/\ball\b/.test(val)) return false;
26342
- return /\b(?:(?:max|min)-)?(?:width|height)\b|\bpadding\b|\bmargin\b/.test(val);
26343
- },
26344
- fmt: (m) => {
26345
- const found = cap2(m, 1).match(
26346
- /\b(?:(?:max|min)-)?(?:width|height)\b|\bpadding(?:-(?:top|right|bottom|left))?\b|\bmargin(?:-(?:top|right|bottom|left))?\b/gi
26347
- );
26348
- return `transition: ${found ? found.join(", ") : cap2(m, 1).trim()}`;
26349
- }
26350
- },
26351
- // ── Broken image ──────────────────────────────────────────────────────────
26352
- {
26353
- id: "broken-image",
26354
- regex: /<img\b[^>]*?\bsrc\s*=\s*(?:""|''|"\s+"|'\s+'|"#"|'#')/gi,
26355
- test: () => true,
26356
- fmt: (m) => cap2(m, 0).slice(0, 100)
26357
- },
26358
- {
26359
- id: "broken-image",
26360
- regex: /<img\b(?:(?!\bsrc\s*=)[^>])*>/gi,
26361
- test: (m) => !/\bsrc\s*=/i.test(cap2(m, 0)),
26362
- fmt: (m) => cap2(m, 0).slice(0, 100)
26363
- }
26364
- ];
26365
- function stripHtmlToText(html) {
26366
- return html.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, " ").replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, " ").replace(/<!--[\s\S]*?-->/g, " ").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ");
26367
- }
26368
- var ANALYZERS = [
26369
- // Overused primary font (font-family declarations + Google Fonts URLs).
26370
- (text, file) => {
26371
- const out = [];
26372
- const seen = /* @__PURE__ */ new Set();
26373
- for (const m of text.matchAll(/font-family\s*:\s*([^;}{]+)/gi)) {
26374
- const first = firstFamily(cap2(m, 1));
26375
- if (first && !GENERIC_FONTS.has(first) && OVERUSED_FONTS.has(first) && !seen.has(first)) {
26376
- seen.add(first);
26377
- out.push({ id: "overused-font", snippet: `font-family: ${first}`, file, line: lineOf(text, m.index ?? 0) });
26378
- }
26379
- }
26380
- for (const m of text.matchAll(/fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi)) {
26381
- for (const fam of googleFontFamilies(cap2(m, 0))) {
26382
- if (OVERUSED_FONTS.has(fam) && !seen.has(fam)) {
26383
- seen.add(fam);
26384
- out.push({ id: "overused-font", snippet: `Google Fonts: ${fam}`, file, line: lineOf(text, m.index ?? 0) });
26385
- }
26386
- }
26387
- }
26388
- return out;
26389
- },
26390
- // Single font across a substantial page.
26391
- (text, file) => {
26392
- const fonts = /* @__PURE__ */ new Set();
26393
- for (const m of text.matchAll(/font-family\s*:\s*([^;}{]+)/gi)) {
26394
- for (const f of cap2(m, 1).split(",").map(
26395
- (x) => x.trim().replace(/^['"]|['"]$/g, "").toLowerCase()
26396
- )) {
26397
- if (f && !GENERIC_FONTS.has(f)) fonts.add(f);
26398
- }
26399
- }
26400
- for (const f of allGoogleFontFamilies(text)) fonts.add(f);
26401
- if (fonts.size !== 1 || text.split("\n").length < 20) return [];
26402
- const only = [...fonts][0] ?? "";
26403
- return [{ id: "single-font", snippet: `only font used is ${only}`, file }];
26404
- },
26405
- // Flat type hierarchy (all sizes span <2×).
26406
- (text, file) => {
26407
- const sizes = /* @__PURE__ */ new Set();
26408
- const REM = 16;
26409
- for (const m of text.matchAll(/font-size\s*:\s*([\d.]+)(px|rem|em)\b/gi)) {
26410
- const px = cap2(m, 2) === "px" ? num(m, 1) : num(m, 1) * REM;
26411
- if (px > 0 && px < 200) sizes.add(Math.round(px * 10) / 10);
26412
- }
26413
- for (const [cls, px] of Object.entries(TAILWIND_TEXT_PX)) {
26414
- if (new RegExp(`\\b${cls}\\b`).test(text)) sizes.add(px);
26415
- }
26416
- if (sizes.size < 3) return [];
26417
- const sorted = [...sizes].sort((a, b) => a - b);
26418
- const min = sorted[0] ?? 1;
26419
- const max = sorted[sorted.length - 1] ?? 1;
26420
- const ratio = max / min;
26421
- if (ratio >= 2) return [];
26422
- return [
26423
- {
26424
- id: "flat-type-hierarchy",
26425
- snippet: `sizes ${sorted.map((s) => `${s}px`).join(", ")} (ratio ${ratio.toFixed(1)}:1)`,
26426
- file
26427
- }
26428
- ];
26429
- },
26430
- // Monotonous spacing (one value dominates).
26431
- (text, file) => {
26432
- const vals = [];
26433
- for (const m of text.matchAll(/(?:padding|margin)(?:-(?:top|right|bottom|left))?\s*:\s*(\d+)px/gi)) {
26434
- const v = num(m, 1);
26435
- if (v > 0 && v < 200) vals.push(v);
26436
- }
26437
- for (const m of text.matchAll(/(?:padding|margin)(?:-(?:top|right|bottom|left))?\s*:\s*([\d.]+)rem/gi)) {
26438
- const v = Math.round(Number.parseFloat(cap2(m, 1)) * 16);
26439
- if (v > 0 && v < 200) vals.push(v);
26440
- }
26441
- for (const m of text.matchAll(/\b(?:p|px|py|pt|pb|pl|pr|m|mx|my|mt|mb|ml|mr|gap)-(\d+)\b/g))
26442
- vals.push(num(m, 1) * 4);
26443
- const rounded = vals.map((v) => Math.round(v / 4) * 4);
26444
- if (rounded.length < 10) return [];
26445
- const counts = {};
26446
- for (const v of rounded) counts[v] = (counts[v] || 0) + 1;
26447
- const maxCount = Math.max(...Object.values(counts));
26448
- const pct = maxCount / rounded.length;
26449
- const unique = [...new Set(rounded)].filter((v) => v > 0);
26450
- if (pct <= 0.6 || unique.length > 3) return [];
26451
- const dominant = Object.entries(counts).sort((a, b) => b[1] - a[1])[0]?.[0] ?? "?";
26452
- return [
26453
- {
26454
- id: "monotonous-spacing",
26455
- snippet: `~${dominant}px used ${maxCount}/${rounded.length} times (${Math.round(pct * 100)}%)`,
26456
- file
26457
- }
26458
- ];
26459
- },
26460
- // Em-dash overuse (density-gated).
26461
- (text, file) => {
26462
- const body = stripHtmlToText(text).replace(/&mdash;|&#0*8212;|&#x0*2014;/gi, "\u2014");
26463
- const count = (body.match(/[—]|--(?=\S)/g) ?? []).length;
26464
- if (count < EM_DASH_FLOOR) return [];
26465
- if (body.length > count * EM_DASH_CHARS_PER_DASH) return [];
26466
- return [{ id: "em-dash-overuse", snippet: `${count} em-dashes in body copy`, file }];
26467
- },
26468
- // Marketing buzzwords.
26469
- (text, file) => {
26470
- const body = stripHtmlToText(text);
26471
- const lower = body.toLowerCase();
26472
- let count = 0;
26473
- let firstSample = "";
26474
- for (const phrase of BUZZWORDS) {
26475
- let from = 0;
26476
- for (; ; ) {
26477
- const idx = lower.indexOf(phrase, from);
26478
- if (idx === -1) break;
26479
- count++;
26480
- if (!firstSample) {
26481
- firstSample = body.slice(Math.max(0, idx - 12), Math.min(body.length, idx + phrase.length + 12)).trim();
26482
- }
26483
- from = idx + phrase.length;
26484
- }
26485
- }
26486
- if (count === 0) return [];
26487
- return [
26488
- {
26489
- id: "marketing-buzzword",
26490
- snippet: `${count} buzzword phrase${count === 1 ? "" : "s"}: "${firstSample}"`,
26491
- file
26492
- }
26493
- ];
26494
- },
26495
- // Aphoristic cadence (manufactured contrast + short rebuttal).
26496
- (text, file) => {
26497
- const body = stripHtmlToText(text);
26498
- let count = 0;
26499
- let firstSample = "";
26500
- for (const m of body.matchAll(/\bNot an? [a-z][^.!?]{1,40}[.!]\s+[A-Z][^.!?]{1,60}[.!]/g)) {
26501
- count++;
26502
- if (!firstSample) firstSample = cap2(m, 0).trim().slice(0, 80);
26503
- }
26504
- for (const m of body.matchAll(/\b[A-Z][^.!?]{4,80}[.!]\s+(No|Just)\s+[a-z][^.!?]{2,60}[.!]/g)) {
26505
- count++;
26506
- if (!firstSample) firstSample = cap2(m, 0).trim().slice(0, 80);
26507
- }
26508
- if (count < 3) return [];
26509
- return [{ id: "aphoristic-cadence", snippet: `${count} aphoristic constructions: "${firstSample}"`, file }];
26510
- },
26511
- // Dark glow: a zero-offset chromatic shadow used as decoration.
26512
- (text, file) => {
26513
- const out = [];
26514
- for (const m of text.matchAll(/(?:box|text)-shadow\s*:\s*([^;{}]+)/gi)) {
26515
- if (shadowIsGlow(cap2(m, 1))) {
26516
- out.push({
26517
- id: "dark-glow",
26518
- snippet: `shadow: ${cap2(m, 1).trim().slice(0, 60)}`,
26519
- file,
26520
- line: lineOf(text, m.index ?? 0)
26521
- });
26522
- }
26523
- }
26524
- return out;
26525
- },
26526
- // Marquee (<marquee> element).
26527
- (text, file) => /<marquee\b/i.test(text) ? [{ id: "marquee", snippet: "<marquee> element", file }] : []
26528
- ];
26529
- function shadowIsGlow(value) {
26530
- const first = (value.split(/,(?![^(]*\))/)[0] ?? "").trim();
26531
- const colorMatch = first.match(/#[0-9a-f]{3,8}\b|rgba?\([^)]+\)|hsla?\([^)]+\)/i);
26532
- if (!colorMatch) return false;
26533
- const lengthsPart = first.replace(colorMatch[0], " ").replace(/\binset\b/gi, " ");
26534
- const offsets = lengthsPart.match(/-?\d*\.?\d+(?:px|rem|em)?/g);
26535
- if (!offsets || offsets.length < 3) return false;
26536
- const x = Number.parseFloat(offsets[0] ?? "0");
26537
- const y = Number.parseFloat(offsets[1] ?? "0");
26538
- const blur = Number.parseFloat(offsets[2] ?? "0");
26539
- if (!(x === 0 && y === 0 && blur > 4)) return false;
26540
- const c = parseColor2(colorMatch[0]);
26541
- return c !== null && hasChroma(c, 30);
26542
- }
26543
- function firstFamily(decl) {
26544
- return (decl.split(",")[0] ?? "").trim().replace(/^['"]|['"]$/g, "").toLowerCase();
26545
- }
26546
- function googleFontFamilies(url) {
26547
- const q = url.indexOf("?");
26548
- if (q === -1) return [];
26549
- const params = new URLSearchParams(url.slice(q + 1).replace(/&amp;/g, "&"));
26550
- const out = [];
26551
- for (const value of params.getAll("family")) {
26552
- for (const part of value.split("|")) out.push((part.split(":")[0] ?? "").trim().toLowerCase());
26553
- }
26554
- return out.filter(Boolean);
26555
- }
26556
- function allGoogleFontFamilies(text) {
26557
- const out = [];
26558
- for (const m of text.matchAll(/fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi))
26559
- out.push(...googleFontFamilies(cap2(m, 0)));
26560
- return out;
26561
- }
26562
- function lineOf(text, index) {
26563
- let line = 1;
26564
- for (let i = 0; i < index && i < text.length; i++) if (text[i] === "\n") line++;
26565
- return line;
26566
- }
26567
- function detectSource(source) {
26568
- const { path: file, text } = source;
26569
- const findings = [];
26570
- const lines = text.split("\n");
26571
- for (const matcher of LINE_MATCHERS) {
26572
- for (let i = 0; i < lines.length; i++) {
26573
- const line = lines[i] ?? "";
26574
- for (const m of line.matchAll(matcher.regex)) {
26575
- if (matcher.test(m, line)) findings.push({ id: matcher.id, snippet: matcher.fmt(m, line), file, line: i + 1 });
26576
- }
26577
- }
26578
- }
26579
- for (const analyzer of ANALYZERS) findings.push(...analyzer(text, file));
26580
- return dedupe(findings);
26581
- }
26582
- function dedupe(findings) {
26583
- const out = [];
26584
- for (const f of findings) {
26585
- const dupe = out.some(
26586
- (d) => d.id === f.id && d.snippet === f.snippet && Math.abs((d.line ?? 0) - (f.line ?? 0)) <= 2
26587
- );
26588
- if (!dupe) out.push(f);
26589
- }
26590
- return out;
26591
- }
26592
-
26593
- // src/engine/landing/lib/critique.ts
26594
- var FAMILIES = ["typography", "color", "borders_depth", "motion", "spacing", "copy", "integrity"];
26595
- function round4(n) {
26596
- return Math.round(n * 100) / 100;
26597
- }
26598
- function clamp012(n) {
26599
- return Math.max(0, Math.min(1, n));
26600
- }
26601
- function applyBrand(id, snippet, severity, brand) {
26602
- const meta = RULE_META[id];
26603
- if (!meta?.brandAware || !brand.hasTokens) return { severity, brandDrift: false };
26604
- const matched = meta.brandAware === "font" && brandCommitsFont(brand, snippet) || meta.brandAware === "color" && id === "ai-color-palette" && brandCommitsAiHue(brand) || meta.brandAware === "color" && id === "cream-palette" && brandCommitsCream(brand);
26605
- return matched ? { severity: "advisory", brandDrift: true } : { severity, brandDrift: false };
26606
- }
26607
- function critiqueLanding(input) {
26608
- const sources = Array.isArray(input.sources) ? input.sources : [];
26609
- const findings = [];
26610
- for (const source of sources) {
26611
- for (const raw of detectSource(source)) {
26612
- const meta = RULE_META[raw.id];
26613
- if (!meta) continue;
26614
- const { severity, brandDrift } = applyBrand(raw.id, raw.snippet, meta.severity, input.brand);
26615
- findings.push({
26616
- id: raw.id,
26617
- severity,
26618
- family: meta.family,
26619
- snippet: raw.snippet,
26620
- file: raw.file,
26621
- line: raw.line,
26622
- note: meta.note,
26623
- ...brandDrift ? { brandDrift: true } : {}
26624
- });
26625
- }
26626
- }
26627
- const dimensions = FAMILIES.map((family) => {
26628
- const fam = findings.filter((f) => f.family === family);
26629
- const penalty = fam.reduce((s, f) => s + SEVERITY_WEIGHT[f.severity], 0);
26630
- const score = round4(clamp012(1 - penalty));
26631
- const note = fam.length === 0 ? "clean" : `${fam.length} finding(s): ${describeCounts(fam)}`;
26632
- return { dimension: family, score, note };
26633
- });
26634
- const overall = round4(dimensions.reduce((s, d) => s + d.score, 0) / dimensions.length);
26635
- const counts = {
26636
- block: findings.filter((f) => f.severity === "block").length,
26637
- warn: findings.filter((f) => f.severity === "warn").length,
26638
- advisory: findings.filter((f) => f.severity === "advisory").length
26639
- };
26640
- return { advisory: true, overall, dimensions, findings, counts };
26641
- }
26642
- function describeCounts(findings) {
26643
- const b = findings.filter((f) => f.severity === "block").length;
26644
- const w = findings.filter((f) => f.severity === "warn").length;
26645
- const a = findings.filter((f) => f.severity === "advisory").length;
26646
- return [b ? `${b} block` : "", w ? `${w} warn` : "", a ? `${a} advisory` : ""].filter(Boolean).join(", ");
26647
- }
26648
-
26649
- // src/commands/landing/snapshot.ts
26650
- import { mkdir as mkdir7, writeFile as writeFile10 } from "fs/promises";
26651
- import path24 from "path";
26652
- var CRITIC_VERSION = "1";
26653
- function critiqueCacheDir(projectRoot) {
26654
- return path24.join(projectRoot, ".cache", "landing-critique");
26655
- }
26656
- function snapshotPath(projectRoot, slug) {
26657
- return path24.join(critiqueCacheDir(projectRoot), `${slug}.json`);
26658
- }
26659
- async function writeCritiqueSnapshot(projectRoot, snapshot) {
26660
- await mkdir7(critiqueCacheDir(projectRoot), { recursive: true });
26661
- await writeFile10(snapshotPath(projectRoot, snapshot.slug), `${JSON.stringify(snapshot, null, 2)}
26662
- `, "utf8");
26663
- }
26664
-
26665
- // src/commands/landing/source-version.ts
26666
- import { readdir as readdir7, readFile as readFile21, stat as stat5 } from "fs/promises";
26667
- import path25 from "path";
26668
- async function landingSourceRelPaths(landingDir) {
26669
- const rel = [];
26670
- if (await isFile(path25.join(landingDir, "index.astro"))) rel.push("index.astro");
26671
- const componentsDir = path25.join(landingDir, "_components");
26672
- for (const abs of await walkAstro(componentsDir)) {
26673
- rel.push(path25.relative(landingDir, abs).split(path25.sep).join("/"));
26674
- }
26675
- return rel.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
26676
- }
26677
- async function readLandingSources(landingDir) {
26678
- const rel = await landingSourceRelPaths(landingDir);
26679
- const out = [];
26680
- for (const r of rel) out.push({ path: r, text: await readFile21(path25.join(landingDir, r), "utf8") });
26681
- return out;
26682
- }
26683
- async function computeLandingSourceSha(landingDir) {
26684
- const rel = await landingSourceRelPaths(landingDir);
26685
- const parts = [];
26686
- for (const r of rel) {
26687
- let bytes;
26688
- try {
26689
- bytes = await readFile21(path25.join(landingDir, r));
26690
- } catch {
26691
- bytes = Buffer.alloc(0);
26692
- }
26693
- parts.push(Buffer.from(`${r}\0${bytes.length}\0`), bytes);
26694
- }
26695
- return sha256Hex(Buffer.concat(parts));
26696
- }
26697
- async function isFile(p) {
26698
- try {
26699
- return (await stat5(p)).isFile();
26700
- } catch {
26701
- return false;
26702
- }
26703
- }
26704
- async function walkAstro(dir) {
26705
- let entries;
26706
- try {
26707
- entries = await readdir7(dir, { withFileTypes: true });
26708
- } catch {
26709
- return [];
26710
- }
26711
- const out = [];
26712
- for (const entry of entries) {
26713
- const abs = path25.join(dir, entry.name);
26714
- if (entry.isDirectory()) out.push(...await walkAstro(abs));
26715
- else if (entry.isFile() && entry.name.endsWith(".astro")) out.push(abs);
26716
- }
26717
- return out;
26718
- }
26719
-
26720
- // src/commands/landing/critique.ts
26721
- registerSchema({
26722
- command: "landing.critique",
26723
- description: "Deterministic design-quality critic for a landing page (ADVISORY \u2014 never blocks, always exits 0). Flags the known AI 'slop' tells (gradient text, overused fonts, side-tab borders, cream palettes, buzzword copy, broken images\u2026) tiered block/warn/advisory, and records the critique the publish quality gate requires.",
26724
- args: {
26725
- slug: { type: "string", description: "Landing slug (folder under src/pages/)", required: true },
26726
- full: {
26727
- type: "boolean",
26728
- description: "Also print advisory-tier findings (default: block + warn only)",
26729
- required: false
26730
- }
26731
- }
26732
- });
26733
- var critiqueCommand2 = defineCommand133({
26734
- meta: {
26735
- name: "critique",
26736
- description: "Deterministic design-quality critic for a landing (ADVISORY \u2014 never blocks). Flags the known AI design tells (gradient text, overused fonts, side-tab borders, cream palettes, buzzword copy, broken images) tiered block/warn/advisory, respecting the client's BRAND.md as the allowlist. Also records the critique that `baker publish` requires \u2014 run it before finishing a landing."
26737
- },
26738
- args: {
26739
- slug: { type: "positional", required: true, description: "Landing slug (folder under src/pages/)" },
26740
- full: { type: "boolean", description: "Also print advisory-tier findings", default: false }
26741
- },
26742
- async run({ args }) {
26743
- const slug = String(args.slug);
26744
- const projectRoot = process.cwd();
26745
- const landingDir = path26.resolve(projectRoot, "src", "pages", slug);
26746
- if (!await isDir(landingDir)) {
26747
- process.stderr.write(
26748
- `${JSON.stringify(
26749
- { ok: false, error: { code: "not-found", message: `No landing at src/pages/${slug}/` } },
26750
- null,
26751
- 2
26752
- )}
26753
- `
26754
- );
26755
- process.exit(2);
26756
- return;
26757
- }
26758
- const [sources, brand, sourceSha] = await Promise.all([
26759
- readLandingSources(landingDir),
26760
- loadBrandTokens(projectRoot),
26761
- computeLandingSourceSha(landingDir)
26762
- ]);
26763
- const report = critiqueLanding({ slug, sources, brand });
26764
- try {
26765
- await writeCritiqueSnapshot(projectRoot, {
26766
- slug,
26767
- sourceSha,
26768
- criticVersion: CRITIC_VERSION,
26769
- at: (/* @__PURE__ */ new Date()).toISOString(),
26770
- blockCount: report.counts.block,
26771
- warnCount: report.counts.warn
26772
- });
26773
- } catch {
26774
- }
26775
- const shown = report.findings.filter((f) => args.full ? true : f.severity !== "advisory");
26776
- process.stdout.write(
26777
- `${JSON.stringify(
26778
- {
26779
- ok: true,
26780
- advisory: true,
26781
- slug,
26782
- overall: report.overall,
26783
- counts: report.counts,
26784
- dimensions: report.dimensions,
26785
- findings: shown.map(present),
26786
- hint: report.counts.block > 0 ? "Block-tier tells are held at publish \u2014 fix them. Warn/advisory are guidance. Scores are 0\u20131 (higher is better)." : "No block-tier tells. Warn/advisory findings are guidance, not gates. Re-run after edits so the publish gate stays fresh."
26787
- },
26788
- null,
26789
- 2
26790
- )}
26791
- `
26792
- );
26793
- }
26794
- });
26795
- function present(f) {
26796
- return {
26797
- id: f.id,
26798
- severity: f.severity,
26799
- family: f.family,
26800
- where: f.line ? `${f.file}:${f.line}` : f.file,
26801
- snippet: f.snippet,
26802
- note: f.note,
26803
- ...f.brandDrift ? { brandDrift: true } : {}
26804
- };
26805
- }
26806
- async function isDir(p) {
26807
- try {
26808
- return (await stat6(p)).isDirectory();
26809
- } catch {
26810
- return false;
26811
- }
26812
- }
26813
-
26814
- // src/commands/landing/index.ts
26815
- var landingCommand = defineCommand134({
26816
- meta: {
26817
- name: "landing",
26818
- description: `Design-quality tools for landing pages (src/pages/<slug>/).
26819
-
26820
- Subcommands:
26821
- baker landing critique <slug> \u2014 deterministic design-quality critic (advisory): flags the known AI 'slop' tells (gradient text, overused fonts, side-tab borders, cream palettes, buzzword copy, broken images) tiered block/warn/advisory, respecting the client's BRAND.md. Records the critique the publish quality gate requires \u2014 run it before finishing a landing.`
26822
- },
26823
- subCommands: {
26824
- critique: critiqueCommand2
26825
- }
26826
- });
26827
-
26828
25830
  // src/commands/mcp/index.ts
26829
- import { defineCommand as defineCommand135 } from "citty";
25831
+ import { defineCommand as defineCommand133 } from "citty";
26830
25832
  var SCOPES = ["user", "user_org", "company", "org"];
26831
25833
  function parseScope(raw) {
26832
25834
  const scope = raw === void 0 ? "company" : String(raw);
@@ -26865,7 +25867,7 @@ registerSchema({
26865
25867
  description: "List the custom MCP servers this company's chats see (org + company + your own user scope).",
26866
25868
  args: {}
26867
25869
  });
26868
- var listCommand8 = defineCommand135({
25870
+ var listCommand8 = defineCommand133({
26869
25871
  meta: { name: "list", description: "List custom MCP servers visible to this company's chats." },
26870
25872
  run: async () => {
26871
25873
  try {
@@ -26890,7 +25892,7 @@ registerSchema({
26890
25892
  header: { type: "string", description: 'Auth header "Key: Value" (repeatable)', required: false }
26891
25893
  }
26892
25894
  });
26893
- var addCommand = defineCommand135({
25895
+ var addCommand = defineCommand133({
26894
25896
  meta: {
26895
25897
  name: "add",
26896
25898
  description: `Register a custom MCP server. Tools appear as mcp__<name>__* on the NEXT message.
@@ -26931,7 +25933,7 @@ registerSchema({
26931
25933
  description: "Remove a company custom MCP server by name.",
26932
25934
  args: { name: { type: "string", description: "Server name to remove", required: true } }
26933
25935
  });
26934
- var removeCommand4 = defineCommand135({
25936
+ var removeCommand4 = defineCommand133({
26935
25937
  meta: {
26936
25938
  name: "remove",
26937
25939
  description: `Remove a company custom MCP server by name.
@@ -26953,7 +25955,7 @@ Example:
26953
25955
  }
26954
25956
  }
26955
25957
  });
26956
- var mcpCommand = defineCommand135({
25958
+ var mcpCommand = defineCommand133({
26957
25959
  meta: {
26958
25960
  name: "mcp",
26959
25961
  description: `Custom MCP servers for this company \u2014 point the agent at any HTTPS MCP endpoint.
@@ -26977,10 +25979,10 @@ Examples:
26977
25979
  });
26978
25980
 
26979
25981
  // src/commands/research/index.ts
26980
- import { defineCommand as defineCommand146 } from "citty";
25982
+ import { defineCommand as defineCommand144 } from "citty";
26981
25983
 
26982
25984
  // src/commands/research/advertisers.ts
26983
- import { defineCommand as defineCommand136 } from "citty";
25985
+ import { defineCommand as defineCommand134 } from "citty";
26984
25986
 
26985
25987
  // src/commands/research/output.ts
26986
25988
  var RESEARCH_DATA_NOTE = "Estimates based on third-party SERP data \u2014 not exact figures. Use for directional insights, not precise measurement.";
@@ -27093,7 +26095,7 @@ var FIELDS3 = {
27093
26095
  etv: "Estimated traffic value (USD)",
27094
26096
  visibility: "SERP visibility score (0-1)"
27095
26097
  };
27096
- var advertisersCommand = defineCommand136({
26098
+ var advertisersCommand = defineCommand134({
27097
26099
  meta: {
27098
26100
  name: "advertisers",
27099
26101
  description: `Find domains competing for a keyword in Google SERPs.
@@ -27140,7 +26142,7 @@ Examples:
27140
26142
  });
27141
26143
 
27142
26144
  // src/commands/research/autocomplete.ts
27143
- import { defineCommand as defineCommand137 } from "citty";
26145
+ import { defineCommand as defineCommand135 } from "citty";
27144
26146
  registerSchema({
27145
26147
  command: "research.autocomplete",
27146
26148
  description: "Get Google Autocomplete suggestions for a seed keyword. Useful for keyword expansion and discovering what people actually search for. IMPORTANT: If --location and --language are omitted, defaults to United States (us) and English (en).",
@@ -27163,7 +26165,7 @@ registerSchema({
27163
26165
  var FIELDS4 = {
27164
26166
  suggestion: "Autocomplete suggestion from Google"
27165
26167
  };
27166
- var autocompleteCommand = defineCommand137({
26168
+ var autocompleteCommand = defineCommand135({
27167
26169
  meta: {
27168
26170
  name: "autocomplete",
27169
26171
  description: `Get Google Autocomplete suggestions for keyword expansion.
@@ -27209,7 +26211,7 @@ Examples:
27209
26211
  });
27210
26212
 
27211
26213
  // src/commands/research/countries.ts
27212
- import { defineCommand as defineCommand138 } from "citty";
26214
+ import { defineCommand as defineCommand136 } from "citty";
27213
26215
  registerSchema({
27214
26216
  command: "research.countries",
27215
26217
  description: "List all supported country codes for --location flag in research commands.",
@@ -27266,7 +26268,7 @@ var FIELDS5 = {
27266
26268
  code: "Country code to pass as --location",
27267
26269
  name: "Country name"
27268
26270
  };
27269
- var countriesCommand = defineCommand138({
26271
+ var countriesCommand = defineCommand136({
27270
26272
  meta: {
27271
26273
  name: "countries",
27272
26274
  description: "List all supported country codes for --location flag."
@@ -27277,7 +26279,7 @@ var countriesCommand = defineCommand138({
27277
26279
  });
27278
26280
 
27279
26281
  // src/commands/research/intent.ts
27280
- import { defineCommand as defineCommand139 } from "citty";
26282
+ import { defineCommand as defineCommand137 } from "citty";
27281
26283
  registerSchema({
27282
26284
  command: "research.intent",
27283
26285
  description: "Classify Google Search intent for keywords. Determines if someone searching is looking to buy, research, or navigate. IMPORTANT: If --language is omitted, defaults to English (en). The response includes a query_context object showing which language was used.",
@@ -27300,7 +26302,7 @@ var FIELDS6 = {
27300
26302
  intent: "Primary Google Search intent: informational, navigational, commercial, transactional",
27301
26303
  probability: "Confidence score 0.0-1.0"
27302
26304
  };
27303
- var intentCommand = defineCommand139({
26305
+ var intentCommand = defineCommand137({
27304
26306
  meta: {
27305
26307
  name: "intent",
27306
26308
  description: `Classify Google Search intent for keywords. Returns intent type and confidence.
@@ -27348,7 +26350,7 @@ Examples:
27348
26350
  });
27349
26351
 
27350
26352
  // src/commands/research/keyword-gap.ts
27351
- import { defineCommand as defineCommand140 } from "citty";
26353
+ import { defineCommand as defineCommand138 } from "citty";
27352
26354
  registerSchema({
27353
26355
  command: "research.keyword-gap",
27354
26356
  description: "Find keywords a competitor ranks for (organic or paid) that you don't. Discovers expansion opportunities. IMPORTANT: If --location and --language are omitted, defaults to United States (us) and English (en). The response includes a query_context object showing which location/language were used.",
@@ -27377,7 +26379,7 @@ var FIELDS7 = {
27377
26379
  cpc: "Cost per click USD",
27378
26380
  their_position: "Competitor's ranking position"
27379
26381
  };
27380
- var keywordGapCommand = defineCommand140({
26382
+ var keywordGapCommand = defineCommand138({
27381
26383
  meta: {
27382
26384
  name: "keyword-gap",
27383
26385
  description: `Find keywords a competitor has that you don't. Supports pagination via --offset.
@@ -27451,7 +26453,7 @@ Examples:
27451
26453
  });
27452
26454
 
27453
26455
  // src/commands/research/keywords-for-site.ts
27454
- import { defineCommand as defineCommand141 } from "citty";
26456
+ import { defineCommand as defineCommand139 } from "citty";
27455
26457
  registerSchema({
27456
26458
  command: "research.keywords-for-site",
27457
26459
  description: "Get keywords a competitor targets in Google. Use --type paid to see only paid keywords, --type organic for organic only. IMPORTANT: If --location and --language are omitted, defaults to United States (us) and English (en). The response includes a query_context object showing which location/language were used.",
@@ -27484,7 +26486,7 @@ var FIELDS8 = {
27484
26486
  competition: "LOW, MEDIUM, or HIGH",
27485
26487
  competition_index: "Competition score 0-100"
27486
26488
  };
27487
- var keywordsForSiteCommand = defineCommand141({
26489
+ var keywordsForSiteCommand = defineCommand139({
27488
26490
  meta: {
27489
26491
  name: "keywords-for-site",
27490
26492
  description: `Get keywords a competitor targets in Google. Use --type to filter paid/organic.
@@ -27537,7 +26539,7 @@ Examples:
27537
26539
  });
27538
26540
 
27539
26541
  // src/commands/research/languages.ts
27540
- import { defineCommand as defineCommand142 } from "citty";
26542
+ import { defineCommand as defineCommand140 } from "citty";
27541
26543
  registerSchema({
27542
26544
  command: "research.languages",
27543
26545
  description: "List all supported language codes for --language flag in research commands.",
@@ -27567,7 +26569,7 @@ var FIELDS9 = {
27567
26569
  code: "Language code to pass as --language",
27568
26570
  name: "Language name (also accepted by --language)"
27569
26571
  };
27570
- var languagesCommand2 = defineCommand142({
26572
+ var languagesCommand2 = defineCommand140({
27571
26573
  meta: {
27572
26574
  name: "languages",
27573
26575
  description: "List all supported language codes for --language flag."
@@ -27578,7 +26580,7 @@ var languagesCommand2 = defineCommand142({
27578
26580
  });
27579
26581
 
27580
26582
  // src/commands/research/lighthouse.ts
27581
- import { defineCommand as defineCommand143 } from "citty";
26583
+ import { defineCommand as defineCommand141 } from "citty";
27582
26584
  registerSchema({
27583
26585
  command: "research.lighthouse",
27584
26586
  description: "Landing page performance audit. Returns metrics that affect Google Ads Quality Score and CPC.",
@@ -27597,7 +26599,7 @@ var FIELDS10 = {
27597
26599
  speed_index_ms: "Speed Index in ms (good: < 3400)",
27598
26600
  interactive_ms: "Time to Interactive in ms (good: < 3800)"
27599
26601
  };
27600
- var lighthouseCommand = defineCommand143({
26602
+ var lighthouseCommand = defineCommand141({
27601
26603
  meta: {
27602
26604
  name: "lighthouse",
27603
26605
  description: `Landing page performance audit. Metrics affecting Google Ads Quality Score.
@@ -27635,7 +26637,7 @@ Examples:
27635
26637
  });
27636
26638
 
27637
26639
  // src/commands/research/relevant-pages.ts
27638
- import { defineCommand as defineCommand144 } from "citty";
26640
+ import { defineCommand as defineCommand142 } from "citty";
27639
26641
  registerSchema({
27640
26642
  command: "research.relevant-pages",
27641
26643
  description: "Get the top pages of a competitor domain with organic traffic and ranking data. Shows which pages drive the most traffic. IMPORTANT: If --location and --language are omitted, defaults to United States (us) and English (en).",
@@ -27661,7 +26663,7 @@ var FIELDS11 = {
27661
26663
  keywords: "Total organic keywords the page ranks for",
27662
26664
  top_10: "Keywords in positions 1-10"
27663
26665
  };
27664
- var relevantPagesCommand = defineCommand144({
26666
+ var relevantPagesCommand = defineCommand142({
27665
26667
  meta: {
27666
26668
  name: "relevant-pages",
27667
26669
  description: `Get the top pages of a competitor domain with traffic data.
@@ -27707,7 +26709,7 @@ Examples:
27707
26709
  });
27708
26710
 
27709
26711
  // src/commands/research/web.ts
27710
- import { defineCommand as defineCommand145 } from "citty";
26712
+ import { defineCommand as defineCommand143 } from "citty";
27711
26713
  registerSchema({
27712
26714
  command: "research.web",
27713
26715
  description: "Search the web with AI to answer marketing questions \u2014 competitors, ICP, pricing, pain points, market trends. Three depth levels: medium (quick, default), high (thorough), xhigh (exhaustive deep research).",
@@ -27758,7 +26760,7 @@ async function runDeepResearch(question) {
27758
26760
  }
27759
26761
  throw new Error("Deep research timed out");
27760
26762
  }
27761
- var webCommand = defineCommand145({
26763
+ var webCommand = defineCommand143({
27762
26764
  meta: {
27763
26765
  name: "web",
27764
26766
  description: `Search the web with AI to answer any open-ended marketing question. Uses live internet data via Google Search.
@@ -27818,7 +26820,7 @@ Examples:
27818
26820
  });
27819
26821
 
27820
26822
  // src/commands/research/index.ts
27821
- var researchCommand = defineCommand146({
26823
+ var researchCommand = defineCommand144({
27822
26824
  meta: {
27823
26825
  name: "research",
27824
26826
  description: `Competitive intelligence and AI-powered research commands.
@@ -27858,10 +26860,10 @@ Examples:
27858
26860
  });
27859
26861
 
27860
26862
  // src/commands/scheduled-actions/index.ts
27861
- import { defineCommand as defineCommand153 } from "citty";
26863
+ import { defineCommand as defineCommand151 } from "citty";
27862
26864
 
27863
26865
  // src/commands/scheduled-actions/create.ts
27864
- import { defineCommand as defineCommand147 } from "citty";
26866
+ import { defineCommand as defineCommand145 } from "citty";
27865
26867
 
27866
26868
  // src/commands/scheduled-actions/shared.ts
27867
26869
  var TEMP_SCHEDULED_ACTION_PREFIX = "temp_sched_";
@@ -27976,7 +26978,7 @@ registerSchema({
27976
26978
  prompt: { type: "string", description: "Additional prompt instructions for the spawned agent", required: false }
27977
26979
  }
27978
26980
  });
27979
- var createCommand2 = defineCommand147({
26981
+ var createCommand2 = defineCommand145({
27980
26982
  meta: {
27981
26983
  name: "create",
27982
26984
  description: 'Stage a scheduled action. Example: baker scheduled-actions create --name "Weekly report" --description "..." --cron "0 9 * * MON"'
@@ -28025,7 +27027,7 @@ var createCommand2 = defineCommand147({
28025
27027
  });
28026
27028
 
28027
27029
  // src/commands/scheduled-actions/delete.ts
28028
- import { defineCommand as defineCommand148 } from "citty";
27030
+ import { defineCommand as defineCommand146 } from "citty";
28029
27031
  registerSchema({
28030
27032
  command: "scheduled-actions.delete",
28031
27033
  description: "Stage deletion of a published scheduled action or cancellation of a temp_sched_* draft creation.",
@@ -28033,7 +27035,7 @@ registerSchema({
28033
27035
  id: { type: "string", description: "Published scheduled action ID or temp_sched_* draft ID", required: true }
28034
27036
  }
28035
27037
  });
28036
- var deleteCommand2 = defineCommand148({
27038
+ var deleteCommand2 = defineCommand146({
28037
27039
  meta: {
28038
27040
  name: "delete",
28039
27041
  description: "Stage scheduled action deletion. Example: baker scheduled-actions delete <id-or-temp_sched_id>"
@@ -28062,7 +27064,7 @@ var deleteCommand2 = defineCommand148({
28062
27064
  });
28063
27065
 
28064
27066
  // src/commands/scheduled-actions/get.ts
28065
- import { defineCommand as defineCommand149 } from "citty";
27067
+ import { defineCommand as defineCommand147 } from "citty";
28066
27068
  registerSchema({
28067
27069
  command: "scheduled-actions.get",
28068
27070
  description: "Get a published scheduled action or a temp_sched_* draft-created scheduled action.",
@@ -28070,7 +27072,7 @@ registerSchema({
28070
27072
  id: { type: "string", description: "Published scheduled action ID or temp_sched_* draft ID", required: true }
28071
27073
  }
28072
27074
  });
28073
- var getCommand3 = defineCommand149({
27075
+ var getCommand3 = defineCommand147({
28074
27076
  meta: {
28075
27077
  name: "get",
28076
27078
  description: "Get a scheduled action. Example: baker scheduled-actions get <id-or-temp_sched_id>"
@@ -28107,13 +27109,13 @@ var getCommand3 = defineCommand149({
28107
27109
  });
28108
27110
 
28109
27111
  // src/commands/scheduled-actions/list.ts
28110
- import { defineCommand as defineCommand150 } from "citty";
27112
+ import { defineCommand as defineCommand148 } from "citty";
28111
27113
  registerSchema({
28112
27114
  command: "scheduled-actions.list",
28113
27115
  description: "List published scheduled actions. Includes draft state when BAKER_CHAT_ID is set.",
28114
27116
  args: {}
28115
27117
  });
28116
- var listCommand9 = defineCommand150({
27118
+ var listCommand9 = defineCommand148({
28117
27119
  meta: {
28118
27120
  name: "list",
28119
27121
  description: "List scheduled actions. Includes staged draft ops when BAKER_CHAT_ID is set."
@@ -28134,7 +27136,7 @@ var listCommand9 = defineCommand150({
28134
27136
  });
28135
27137
 
28136
27138
  // src/commands/scheduled-actions/trigger.ts
28137
- import { defineCommand as defineCommand151 } from "citty";
27139
+ import { defineCommand as defineCommand149 } from "citty";
28138
27140
  registerSchema({
28139
27141
  command: "scheduled-actions.trigger",
28140
27142
  description: "Immediately trigger a published scheduled action. Does not require BAKER_CHAT_ID and rejects temp_sched_* IDs.",
@@ -28142,7 +27144,7 @@ registerSchema({
28142
27144
  id: { type: "string", description: "Published scheduled action ID", required: true }
28143
27145
  }
28144
27146
  });
28145
- var triggerCommand = defineCommand151({
27147
+ var triggerCommand = defineCommand149({
28146
27148
  meta: {
28147
27149
  name: "trigger",
28148
27150
  description: "Immediately trigger a published scheduled action. Example: baker scheduled-actions trigger <id>"
@@ -28179,7 +27181,7 @@ var triggerCommand = defineCommand151({
28179
27181
  });
28180
27182
 
28181
27183
  // src/commands/scheduled-actions/update.ts
28182
- import { defineCommand as defineCommand152 } from "citty";
27184
+ import { defineCommand as defineCommand150 } from "citty";
28183
27185
  registerSchema({
28184
27186
  command: "scheduled-actions.update",
28185
27187
  description: "Stage an update to a published scheduled action or temp_sched_* draft-created scheduled action.",
@@ -28204,7 +27206,7 @@ registerSchema({
28204
27206
  prompt: { type: "string", description: "Replacement additional spawned-agent instructions", required: false }
28205
27207
  }
28206
27208
  });
28207
- var updateCommand2 = defineCommand152({
27209
+ var updateCommand2 = defineCommand150({
28208
27210
  meta: {
28209
27211
  name: "update",
28210
27212
  description: "Stage a scheduled action update. Example: baker scheduled-actions update <id> --enabled false"
@@ -28275,7 +27277,7 @@ var updateCommand2 = defineCommand152({
28275
27277
  });
28276
27278
 
28277
27279
  // src/commands/scheduled-actions/index.ts
28278
- var scheduledActionsCommand = defineCommand153({
27280
+ var scheduledActionsCommand = defineCommand151({
28279
27281
  meta: {
28280
27282
  name: "scheduled-actions",
28281
27283
  description: `Manage Scheduled Actions. Subcommands: list, get, create, update, delete, trigger.
@@ -28301,8 +27303,8 @@ Examples:
28301
27303
  });
28302
27304
 
28303
27305
  // src/commands/schema.ts
28304
- import { defineCommand as defineCommand154 } from "citty";
28305
- var schemaCommand = defineCommand154({
27306
+ import { defineCommand as defineCommand152 } from "citty";
27307
+ var schemaCommand = defineCommand152({
28306
27308
  meta: {
28307
27309
  name: "schema",
28308
27310
  description: "Inspect command argument schemas (for AI agent introspection). Lists all commands if no argument given. Example: baker schema images.search"
@@ -28338,7 +27340,7 @@ var schemaCommand = defineCommand154({
28338
27340
  });
28339
27341
 
28340
27342
  // src/commands/tags/index.ts
28341
- import { defineCommand as defineCommand155 } from "citty";
27343
+ import { defineCommand as defineCommand153 } from "citty";
28342
27344
 
28343
27345
  // src/commands/tags/shared.ts
28344
27346
  function failApi3(err) {
@@ -28404,7 +27406,7 @@ async function listTags(json) {
28404
27406
  failApi3(err);
28405
27407
  }
28406
27408
  }
28407
- var listCommand10 = defineCommand155({
27409
+ var listCommand10 = defineCommand153({
28408
27410
  meta: {
28409
27411
  name: "list",
28410
27412
  description: "Effective tags for this chat (production + staged), with each tag's full readable config (secrets excluded) \u2014 reuse a stored value to pre-fill a change rather than asking the user. Refs printed here are what flow side-effect tagIds should use. Example: baker tags list"
@@ -28423,7 +27425,7 @@ async function listDraft3() {
28423
27425
  failApi3(err);
28424
27426
  }
28425
27427
  }
28426
- var draftCommand3 = defineCommand155({
27428
+ var draftCommand3 = defineCommand153({
28427
27429
  meta: {
28428
27430
  name: "draft",
28429
27431
  description: "Review the tag changes staged in this chat (read-only). Staged changes were approved via request_tag_input and apply when the chat is published; to amend or drop one, propose a follow-up change through the same tool (a delete on a tag_temp_* ref drops the staged create)."
@@ -28432,7 +27434,7 @@ var draftCommand3 = defineCommand155({
28432
27434
  await listDraft3();
28433
27435
  }
28434
27436
  });
28435
- var tagsCommand3 = defineCommand155({
27437
+ var tagsCommand3 = defineCommand153({
28436
27438
  meta: {
28437
27439
  name: "tags",
28438
27440
  description: `Read the client's marketing/analytics tags (Meta pixel, GA4, Google Ads, GTM, Clarity, Hotjar, \u2026) \u2014 production tags plus the changes staged in this chat.
@@ -28457,10 +27459,10 @@ Examples:
28457
27459
  });
28458
27460
 
28459
27461
  // src/commands/testimonials/index.ts
28460
- import { defineCommand as defineCommand159 } from "citty";
27462
+ import { defineCommand as defineCommand157 } from "citty";
28461
27463
 
28462
27464
  // src/commands/testimonials/get.ts
28463
- import { defineCommand as defineCommand156 } from "citty";
27465
+ import { defineCommand as defineCommand154 } from "citty";
28464
27466
  registerSchema({
28465
27467
  command: "testimonials.get",
28466
27468
  description: "Get a single testimonial by ID",
@@ -28468,7 +27470,7 @@ registerSchema({
28468
27470
  id: { type: "string", description: "Testimonial ID", required: true }
28469
27471
  }
28470
27472
  });
28471
- var getCommand4 = defineCommand156({
27473
+ var getCommand4 = defineCommand154({
28472
27474
  meta: { name: "get", description: "Get a single testimonial by ID. Example: baker testimonials get j571abc123" },
28473
27475
  args: {
28474
27476
  id: { type: "positional", description: "Testimonial ID", required: false },
@@ -28505,7 +27507,7 @@ var getCommand4 = defineCommand156({
28505
27507
  });
28506
27508
 
28507
27509
  // src/commands/testimonials/list.ts
28508
- import { defineCommand as defineCommand157 } from "citty";
27510
+ import { defineCommand as defineCommand155 } from "citty";
28509
27511
  registerSchema({
28510
27512
  command: "testimonials.list",
28511
27513
  description: "List testimonials with optional filters.",
@@ -28535,7 +27537,7 @@ registerSchema({
28535
27537
  limit: { type: "number", description: "Max results (default 50)", required: false, default: 50 }
28536
27538
  }
28537
27539
  });
28538
- var listCommand11 = defineCommand157({
27540
+ var listCommand11 = defineCommand155({
28539
27541
  meta: {
28540
27542
  name: "list",
28541
27543
  description: "List testimonials with optional filters. Example: baker testimonials list --source google --sentiment positive"
@@ -28584,7 +27586,7 @@ var listCommand11 = defineCommand157({
28584
27586
  });
28585
27587
 
28586
27588
  // src/commands/testimonials/search.ts
28587
- import { defineCommand as defineCommand158 } from "citty";
27589
+ import { defineCommand as defineCommand156 } from "citty";
28588
27590
  function languageBiasHint(results, requestedLanguage) {
28589
27591
  if (requestedLanguage) {
28590
27592
  return null;
@@ -28662,7 +27664,7 @@ function buildSearchRequest(query, args) {
28662
27664
  }
28663
27665
  return body;
28664
27666
  }
28665
- var searchCommand2 = defineCommand158({
27667
+ var searchCommand2 = defineCommand156({
28666
27668
  meta: {
28667
27669
  name: "search",
28668
27670
  description: "Semantic search testimonials by text query. Uses hybrid BM25 + vector + reranking. Example: baker testimonials search 'great service' --rating-min 4"
@@ -28718,7 +27720,7 @@ var searchCommand2 = defineCommand158({
28718
27720
  var tagsCommand4 = makeTagsCommand("testimonials", "testimonial", "/api/testimonials/tags");
28719
27721
 
28720
27722
  // src/commands/testimonials/index.ts
28721
- var testimonialsCommand = defineCommand159({
27723
+ var testimonialsCommand = defineCommand157({
28722
27724
  meta: {
28723
27725
  name: "testimonials",
28724
27726
  description: `Find and browse testimonials in Baker. Subcommands: search, get, list, tags.
@@ -28739,10 +27741,10 @@ Examples:
28739
27741
  });
28740
27742
 
28741
27743
  // src/commands/videos/index.ts
28742
- import { defineCommand as defineCommand164 } from "citty";
27744
+ import { defineCommand as defineCommand162 } from "citty";
28743
27745
 
28744
27746
  // src/commands/videos/delete.ts
28745
- import { defineCommand as defineCommand160 } from "citty";
27747
+ import { defineCommand as defineCommand158 } from "citty";
28746
27748
  registerSchema({
28747
27749
  command: "videos.delete",
28748
27750
  description: "Delete a video by ID",
@@ -28756,7 +27758,7 @@ registerSchema({
28756
27758
  }
28757
27759
  }
28758
27760
  });
28759
- var deleteCommand3 = defineCommand160({
27761
+ var deleteCommand3 = defineCommand158({
28760
27762
  meta: {
28761
27763
  name: "delete",
28762
27764
  description: "Delete a video by ID. Use --dry-run to preview. Example: baker videos delete j571abc123 --dry-run"
@@ -28797,7 +27799,7 @@ var deleteCommand3 = defineCommand160({
28797
27799
  });
28798
27800
 
28799
27801
  // src/commands/videos/get.ts
28800
- import { defineCommand as defineCommand161 } from "citty";
27802
+ import { defineCommand as defineCommand159 } from "citty";
28801
27803
  registerSchema({
28802
27804
  command: "videos.get",
28803
27805
  description: "Get a single video by ID",
@@ -28805,7 +27807,7 @@ registerSchema({
28805
27807
  id: { type: "string", description: "Video ID", required: true }
28806
27808
  }
28807
27809
  });
28808
- var getCommand5 = defineCommand161({
27810
+ var getCommand5 = defineCommand159({
28809
27811
  meta: { name: "get", description: "Get a single video by ID. Example: baker videos get j571abc123" },
28810
27812
  args: {
28811
27813
  id: { type: "positional", description: "Video ID", required: false },
@@ -28842,7 +27844,7 @@ var getCommand5 = defineCommand161({
28842
27844
  });
28843
27845
 
28844
27846
  // src/commands/videos/search.ts
28845
- import { defineCommand as defineCommand162 } from "citty";
27847
+ import { defineCommand as defineCommand160 } from "citty";
28846
27848
  registerSchema({
28847
27849
  command: "videos.search",
28848
27850
  description: "Search videos by text query. Only returns ready videos.",
@@ -28852,7 +27854,7 @@ registerSchema({
28852
27854
  tags: { type: "string", description: "Comma-separated tags to filter by", required: false }
28853
27855
  }
28854
27856
  });
28855
- var searchCommand3 = defineCommand162({
27857
+ var searchCommand3 = defineCommand160({
28856
27858
  meta: {
28857
27859
  name: "search",
28858
27860
  description: "Semantic search videos by text query. Uses hybrid BM25 + vector + reranking. Example: baker videos search 'product demo' --tags tutorial"
@@ -28902,9 +27904,9 @@ var searchCommand3 = defineCommand162({
28902
27904
  var tagsCommand5 = makeTagsCommand("videos", "video", "/api/videos/tags");
28903
27905
 
28904
27906
  // src/commands/videos/upload.ts
28905
- import { readFile as readFile22, stat as stat7 } from "fs/promises";
27907
+ import { readFile as readFile20, stat as stat5 } from "fs/promises";
28906
27908
  import { extname as extname3 } from "path";
28907
- import { defineCommand as defineCommand163 } from "citty";
27909
+ import { defineCommand as defineCommand161 } from "citty";
28908
27910
  var MIME_MAP = {
28909
27911
  ".mp4": "video/mp4",
28910
27912
  ".mov": "video/quicktime",
@@ -28938,7 +27940,7 @@ function detectContentType(filePath) {
28938
27940
  }
28939
27941
  return mime;
28940
27942
  }
28941
- var uploadCommand2 = defineCommand163({
27943
+ var uploadCommand2 = defineCommand161({
28942
27944
  meta: {
28943
27945
  name: "upload",
28944
27946
  description: "Upload a video file to Baker via Mux direct upload. Auto-detects content type. Example: baker videos upload ./demo.mp4"
@@ -28957,7 +27959,7 @@ var uploadCommand2 = defineCommand163({
28957
27959
  }
28958
27960
  const contentType = args["content-type"] || detectContentType(filePath);
28959
27961
  if (args["dry-run"]) {
28960
- const fileStats = await stat7(filePath);
27962
+ const fileStats = await stat5(filePath);
28961
27963
  writeJson({
28962
27964
  ok: true,
28963
27965
  dryRun: true,
@@ -28967,7 +27969,7 @@ var uploadCommand2 = defineCommand163({
28967
27969
  return;
28968
27970
  }
28969
27971
  const { uploadUrl, videoId } = await apiPost("/api/videos/upload", {});
28970
- const fileBuffer = await readFile22(filePath);
27972
+ const fileBuffer = await readFile20(filePath);
28971
27973
  const uploadResponse = await fetch(uploadUrl, {
28972
27974
  method: "PUT",
28973
27975
  headers: { "Content-Type": contentType },
@@ -28992,7 +27994,7 @@ var uploadCommand2 = defineCommand163({
28992
27994
  });
28993
27995
 
28994
27996
  // src/commands/videos/index.ts
28995
- var videosCommand = defineCommand164({
27997
+ var videosCommand = defineCommand162({
28996
27998
  meta: {
28997
27999
  name: "videos",
28998
28000
  description: `Find and manage videos in Baker. Subcommands: search, get, upload, delete, tags.
@@ -29015,10 +28017,10 @@ Examples:
29015
28017
  });
29016
28018
 
29017
28019
  // src/commands/winning-ads/index.ts
29018
- import { defineCommand as defineCommand175 } from "citty";
28020
+ import { defineCommand as defineCommand173 } from "citty";
29019
28021
 
29020
28022
  // src/commands/winning-ads/advertisers.ts
29021
- import { defineCommand as defineCommand165 } from "citty";
28023
+ import { defineCommand as defineCommand163 } from "citty";
29022
28024
 
29023
28025
  // src/commands/winning-ads/shared.ts
29024
28026
  function splitList(value) {
@@ -29071,7 +28073,7 @@ function advertiserNormalizer(record, full) {
29071
28073
  last_synced_at: record.last_synced_at ?? null
29072
28074
  };
29073
28075
  }
29074
- var advertisersCommand2 = defineCommand165({
28076
+ var advertisersCommand2 = defineCommand163({
29075
28077
  meta: {
29076
28078
  name: "advertisers",
29077
28079
  description: 'List corpus advertisers by name or domain. Find your own advertiser for --exclude-advertiser, or a competitor for --advertiser-id / winners. Example: baker winning-ads advertisers "Deel" --output md'
@@ -29129,7 +28131,7 @@ var advertisersCommand2 = defineCommand165({
29129
28131
  });
29130
28132
 
29131
28133
  // src/commands/winning-ads/brief.ts
29132
- import { defineCommand as defineCommand166 } from "citty";
28134
+ import { defineCommand as defineCommand164 } from "citty";
29133
28135
  registerSchema({
29134
28136
  command: "winning-ads.brief",
29135
28137
  description: "Generate a creative brief grounded in strategically-similar winning ads. Optionally describe the target creative with --dna (JSON) and steer with --notes.",
@@ -29175,7 +28177,7 @@ function parseDna(raw) {
29175
28177
  }
29176
28178
  return parsed;
29177
28179
  }
29178
- var briefCommand = defineCommand166({
28180
+ var briefCommand = defineCommand164({
29179
28181
  meta: {
29180
28182
  name: "brief",
29181
28183
  description: `Generate a creative brief from winning references. Example: baker winning-ads brief --dna '{"angle":"cost savings"}' --notes "B2B, LinkedIn video" --k 8`
@@ -29211,7 +28213,7 @@ var briefCommand = defineCommand166({
29211
28213
  });
29212
28214
 
29213
28215
  // src/commands/winning-ads/feed.ts
29214
- import { defineCommand as defineCommand167 } from "citty";
28216
+ import { defineCommand as defineCommand165 } from "citty";
29215
28217
  function buildFeedParams(input) {
29216
28218
  const params = {};
29217
28219
  const advertiser = splitList(input.advertiser);
@@ -29263,7 +28265,7 @@ registerSchema({
29263
28265
  format: { type: "string", description: "Comma-separated formats to include (e.g. static,video)", required: false }
29264
28266
  }
29265
28267
  });
29266
- var feedCommand = defineCommand167({
28268
+ var feedCommand = defineCommand165({
29267
28269
  meta: {
29268
28270
  name: "feed",
29269
28271
  description: "Winners across every brand you follow (browse, then trim per advertiser). Example: baker winning-ads feed --per-advertiser 5 --output md"
@@ -29348,7 +28350,7 @@ var feedCommand = defineCommand167({
29348
28350
  });
29349
28351
 
29350
28352
  // src/commands/winning-ads/follow.ts
29351
- import { defineCommand as defineCommand168 } from "citty";
28353
+ import { defineCommand as defineCommand166 } from "citty";
29352
28354
  var PLATFORMS = ["meta", "linkedin"];
29353
28355
  registerSchema({
29354
28356
  command: "winning-ads.follow",
@@ -29363,7 +28365,7 @@ registerSchema({
29363
28365
  label: { type: "string", description: "Optional display label (defaults to the resolved name)", required: false }
29364
28366
  }
29365
28367
  });
29366
- var followCommand = defineCommand168({
28368
+ var followCommand = defineCommand166({
29367
28369
  meta: {
29368
28370
  name: "follow",
29369
28371
  description: 'Follow a brand to track ALL its ads \u2014 every platform and country. --platform is how we read your input, not a limit. A domain tracks both Meta + LinkedIn. Example: baker winning-ads follow "deel.com" --platform meta'
@@ -29410,7 +28412,7 @@ var followCommand = defineCommand168({
29410
28412
  });
29411
28413
 
29412
28414
  // src/commands/winning-ads/following.ts
29413
- import { defineCommand as defineCommand169 } from "citty";
28415
+ import { defineCommand as defineCommand167 } from "citty";
29414
28416
  registerSchema({
29415
28417
  command: "winning-ads.following",
29416
28418
  description: "List the brands you follow in your ad-dna library, with each one's status (ready vs still adding) and cached ad counts.",
@@ -29443,7 +28445,7 @@ function followingNormalizer(record, full) {
29443
28445
  platforms: Array.isArray(record.platforms) ? record.platforms : []
29444
28446
  };
29445
28447
  }
29446
- var followingCommand = defineCommand169({
28448
+ var followingCommand = defineCommand167({
29447
28449
  meta: {
29448
28450
  name: "following",
29449
28451
  description: "List brands you follow, with status (ready / adding\u2026) and cached counts. Example: baker winning-ads following --output md"
@@ -29478,7 +28480,7 @@ var followingCommand = defineCommand169({
29478
28480
  });
29479
28481
 
29480
28482
  // src/commands/winning-ads/patterns.ts
29481
- import { defineCommand as defineCommand170 } from "citty";
28483
+ import { defineCommand as defineCommand168 } from "citty";
29482
28484
  registerSchema({
29483
28485
  command: "winning-ads.patterns",
29484
28486
  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.",
@@ -29517,7 +28519,7 @@ function discriminatorRow(record) {
29517
28519
  top_values_duds: Array.isArray(record.top_values_b) ? record.top_values_b.join(", ") : ""
29518
28520
  };
29519
28521
  }
29520
- var patternsCommand = defineCommand170({
28522
+ var patternsCommand = defineCommand168({
29521
28523
  meta: {
29522
28524
  name: "patterns",
29523
28525
  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"
@@ -29573,7 +28575,7 @@ var patternsCommand = defineCommand170({
29573
28575
  });
29574
28576
 
29575
28577
  // src/commands/winning-ads/search.ts
29576
- import { defineCommand as defineCommand171 } from "citty";
28578
+ import { defineCommand as defineCommand169 } from "citty";
29577
28579
  registerSchema({
29578
28580
  command: "winning-ads.search",
29579
28581
  description: "Search the ad-dna corpus of scored winning ads. Returns a lean shortlist (advertiser, summary, scores, media_url) to pick a reference to reproduce.",
@@ -29681,7 +28683,7 @@ function buildSearchBody(args) {
29681
28683
  }
29682
28684
  return body;
29683
28685
  }
29684
- var searchCommand4 = defineCommand171({
28686
+ var searchCommand4 = defineCommand169({
29685
28687
  meta: {
29686
28688
  name: "search",
29687
28689
  description: "Search winning reference ads. Example: baker winning-ads search 'B2B SaaS before/after AI automation' --platform meta --format static --winner-category winner --exclude-advertiser adv_123 --output md"
@@ -29796,7 +28798,7 @@ var searchCommand4 = defineCommand171({
29796
28798
  });
29797
28799
 
29798
28800
  // src/commands/winning-ads/seeds.ts
29799
- import { defineCommand as defineCommand172 } from "citty";
28801
+ import { defineCommand as defineCommand170 } from "citty";
29800
28802
  function leanRow(r) {
29801
28803
  return {
29802
28804
  key: r.key,
@@ -29824,7 +28826,7 @@ function makeSeedCommand(opts) {
29824
28826
  limit: { type: "number", description: "Max keys 1-100 (default 20)", required: false, default: 20 }
29825
28827
  }
29826
28828
  });
29827
- return defineCommand172({
28829
+ return defineCommand170({
29828
28830
  meta: { name: opts.name, description: opts.description },
29829
28831
  args: {
29830
28832
  platform: { type: "string", description: "Single platform to segment on", required: false },
@@ -29873,7 +28875,7 @@ var formatsCommand = makeSeedCommand({
29873
28875
  });
29874
28876
 
29875
28877
  // src/commands/winning-ads/unfollow.ts
29876
- import { defineCommand as defineCommand173 } from "citty";
28878
+ import { defineCommand as defineCommand171 } from "citty";
29877
28879
  registerSchema({
29878
28880
  command: "winning-ads.unfollow",
29879
28881
  description: "Stop following a brand \u2014 removes it from your ad-dna library by advertiser id.",
@@ -29881,7 +28883,7 @@ registerSchema({
29881
28883
  advertiser: { type: "string", description: "Advertiser id to unfollow", required: true }
29882
28884
  }
29883
28885
  });
29884
- var unfollowCommand = defineCommand173({
28886
+ var unfollowCommand = defineCommand171({
29885
28887
  meta: {
29886
28888
  name: "unfollow",
29887
28889
  description: "Stop following a brand by advertiser id. Example: baker winning-ads unfollow adv_123"
@@ -29902,7 +28904,7 @@ var unfollowCommand = defineCommand173({
29902
28904
  });
29903
28905
 
29904
28906
  // src/commands/winning-ads/winners.ts
29905
- import { defineCommand as defineCommand174 } from "citty";
28907
+ import { defineCommand as defineCommand172 } from "citty";
29906
28908
  registerSchema({
29907
28909
  command: "winning-ads.winners",
29908
28910
  description: "Top winning ads for one advertiser id (from `advertisers` or `following`). Returns lean winner cards; add --full for DNA + longevity.",
@@ -29912,7 +28914,7 @@ registerSchema({
29912
28914
  platform: { type: "string", description: "Filter to a single platform: meta|linkedin", required: false }
29913
28915
  }
29914
28916
  });
29915
- var winnersCommand = defineCommand174({
28917
+ var winnersCommand = defineCommand172({
29916
28918
  meta: {
29917
28919
  name: "winners",
29918
28920
  description: "Top winning ads for a specific advertiser id. Example: baker winning-ads winners adv_123 --top 15 --output md"
@@ -29962,7 +28964,7 @@ var winnersCommand = defineCommand174({
29962
28964
  });
29963
28965
 
29964
28966
  // src/commands/winning-ads/index.ts
29965
- var winningAdsCommand = defineCommand175({
28967
+ var winningAdsCommand = defineCommand173({
29966
28968
  meta: {
29967
28969
  name: "winning-ads",
29968
28970
  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.
@@ -30027,7 +29029,7 @@ function getCliVersion() {
30027
29029
  }
30028
29030
 
30029
29031
  // src/cli.ts
30030
- var main = defineCommand176({
29032
+ var main = defineCommand174({
30031
29033
  meta: {
30032
29034
  name: "baker",
30033
29035
  version: getCliVersion(),
@@ -30049,7 +29051,6 @@ Introspection: Run 'baker schema <command>' to inspect argument schemas.`
30049
29051
  creatives: creativesCommand3,
30050
29052
  flows: flowsCommand,
30051
29053
  images: imagesCommand,
30052
- landing: landingCommand,
30053
29054
  videos: videosCommand,
30054
29055
  testimonials: testimonialsCommand,
30055
29056
  canvas: canvasCommand,