@koda-sl/baker-cli 0.141.0 → 0.142.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -73,7 +73,7 @@ import {
73
73
  } from "./chunk-RK67WL4O.js";
74
74
 
75
75
  // src/cli.ts
76
- import { defineCommand as defineCommand175, runMain } from "citty";
76
+ import { defineCommand as defineCommand177, runMain } from "citty";
77
77
 
78
78
  // src/commands/actions/index.ts
79
79
  import { defineCommand as defineCommand18 } from "citty";
@@ -5485,11 +5485,11 @@ function microsFlag(value, flag) {
5485
5485
  if (value === void 0 || value === null || value === "") {
5486
5486
  return void 0;
5487
5487
  }
5488
- const num = Number(value);
5489
- if (Number.isNaN(num) || num <= 0) {
5488
+ const num2 = Number(value);
5489
+ if (Number.isNaN(num2) || num2 <= 0) {
5490
5490
  failWriteValidation(`${flag} must be a positive amount (major currency units, e.g. 50 or 50.00)`);
5491
5491
  }
5492
- return toMicros(num);
5492
+ return toMicros(num2);
5493
5493
  }
5494
5494
  function listFlag(value) {
5495
5495
  if (typeof value !== "string" || value.length === 0) {
@@ -5517,11 +5517,11 @@ function rawTextEntries(value) {
5517
5517
  const values = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
5518
5518
  return values.filter((v) => typeof v === "string").flatMap((v) => v.split(",")).map((v) => v.trim()).filter(Boolean);
5519
5519
  }
5520
- function rawFileEntries(path23) {
5521
- if (typeof path23 !== "string" || path23.length === 0) {
5520
+ function rawFileEntries(path27) {
5521
+ if (typeof path27 !== "string" || path27.length === 0) {
5522
5522
  return [];
5523
5523
  }
5524
- return readFileSync2(path23, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
5524
+ return readFileSync2(path27, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
5525
5525
  }
5526
5526
  function keywordEntries(args) {
5527
5527
  const defaultMatch = typeof args["match-type"] === "string" ? args["match-type"].toUpperCase() : void 0;
@@ -5544,19 +5544,19 @@ function keywordEntries(args) {
5544
5544
  }
5545
5545
  return entries;
5546
5546
  }
5547
- function loadJsonFileArg(path23) {
5548
- if (typeof path23 !== "string" || path23.length === 0) {
5547
+ function loadJsonFileArg(path27) {
5548
+ if (typeof path27 !== "string" || path27.length === 0) {
5549
5549
  return {};
5550
5550
  }
5551
5551
  try {
5552
- const parsed = JSON.parse(readFileSync2(path23, "utf8"));
5552
+ const parsed = JSON.parse(readFileSync2(path27, "utf8"));
5553
5553
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
5554
- failWriteValidation(`${path23} must contain a JSON object`);
5554
+ failWriteValidation(`${path27} must contain a JSON object`);
5555
5555
  }
5556
5556
  return parsed;
5557
5557
  } catch (err) {
5558
5558
  if (err instanceof SyntaxError) {
5559
- failWriteValidation(`${path23} is not valid JSON: ${err.message}`);
5559
+ failWriteValidation(`${path27} is not valid JSON: ${err.message}`);
5560
5560
  }
5561
5561
  throw err;
5562
5562
  }
@@ -5667,10 +5667,10 @@ async function stageUpdate(kind, customerId, target, payload) {
5667
5667
  async function stageTarget(kind, customerId, target) {
5668
5668
  await stageGoogleOp({ kind, customerId, target });
5669
5669
  }
5670
- async function draftAction(path23, body) {
5670
+ async function draftAction(path27, body) {
5671
5671
  try {
5672
5672
  const chatId = requireChatId();
5673
- const response = await apiPost(path23, { chatId, ...body });
5673
+ const response = await apiPost(path27, { chatId, ...body });
5674
5674
  writeJsonEnvelope(response);
5675
5675
  } catch (err) {
5676
5676
  handleGoogleError(err);
@@ -9234,19 +9234,19 @@ function failWriteValidation2(message) {
9234
9234
  writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
9235
9235
  process.exit(1);
9236
9236
  }
9237
- function loadJsonFileArg2(path23) {
9238
- if (typeof path23 !== "string" || path23.length === 0) {
9237
+ function loadJsonFileArg2(path27) {
9238
+ if (typeof path27 !== "string" || path27.length === 0) {
9239
9239
  return {};
9240
9240
  }
9241
9241
  try {
9242
- const parsed = JSON.parse(readFileSync6(path23, "utf8"));
9242
+ const parsed = JSON.parse(readFileSync6(path27, "utf8"));
9243
9243
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
9244
- failWriteValidation2(`${path23} must contain a JSON object`);
9244
+ failWriteValidation2(`${path27} must contain a JSON object`);
9245
9245
  }
9246
9246
  return parsed;
9247
9247
  } catch (err) {
9248
9248
  if (err instanceof SyntaxError) {
9249
- failWriteValidation2(`${path23} is not valid JSON: ${err.message}`);
9249
+ failWriteValidation2(`${path27} is not valid JSON: ${err.message}`);
9250
9250
  }
9251
9251
  throw err;
9252
9252
  }
@@ -9331,15 +9331,15 @@ function parseLocaleFlag(value) {
9331
9331
  }
9332
9332
  return { language: match[1], country: match[2].toUpperCase() };
9333
9333
  }
9334
- function loadTargetingFileArg(path23) {
9335
- if (typeof path23 !== "string" || path23.length === 0) {
9334
+ function loadTargetingFileArg(path27) {
9335
+ if (typeof path27 !== "string" || path27.length === 0) {
9336
9336
  return void 0;
9337
9337
  }
9338
- const parsed = loadJsonFileArg2(path23);
9338
+ const parsed = loadJsonFileArg2(path27);
9339
9339
  const criteria = parsed.targetingCriteria ?? parsed;
9340
9340
  if (!criteria.include) {
9341
9341
  failWriteValidation2(
9342
- `${path23} must contain targeting criteria with an "include" block (see baker schema ads.linkedin.campaigns.create)`
9342
+ `${path27} must contain targeting criteria with an "include" block (see baker schema ads.linkedin.campaigns.create)`
9343
9343
  );
9344
9344
  }
9345
9345
  return criteria;
@@ -9374,14 +9374,14 @@ function parseCsvLine(line) {
9374
9374
  cells.push(current);
9375
9375
  return cells.map((cell) => cell.trim());
9376
9376
  }
9377
- function parseListFileArg(path23, maxRows) {
9378
- if (typeof path23 !== "string" || path23.length === 0) {
9377
+ function parseListFileArg(path27, maxRows) {
9378
+ if (typeof path27 !== "string" || path27.length === 0) {
9379
9379
  return void 0;
9380
9380
  }
9381
- const raw = readFileSync6(path23, "utf8");
9381
+ const raw = readFileSync6(path27, "utf8");
9382
9382
  const lines = raw.split(/\r?\n/).filter((line) => line.trim().length > 0);
9383
9383
  if (lines.length < 2) {
9384
- failWriteValidation2(`${path23} needs a header row and at least one data row`);
9384
+ failWriteValidation2(`${path27} needs a header row and at least one data row`);
9385
9385
  }
9386
9386
  const columns = parseCsvLine(lines[0]).map((column) => column.trim());
9387
9387
  const rows = [];
@@ -9400,7 +9400,7 @@ function parseListFileArg(path23, maxRows) {
9400
9400
  }
9401
9401
  }
9402
9402
  if (rows.length > maxRows) {
9403
- failWriteValidation2(`${path23} has ${rows.length} rows \u2014 the inline limit is ${maxRows}. Split the list.`);
9403
+ failWriteValidation2(`${path27} has ${rows.length} rows \u2014 the inline limit is ${maxRows}. Split the list.`);
9404
9404
  }
9405
9405
  return { columns, rows };
9406
9406
  }
@@ -11500,11 +11500,11 @@ var updateStatusSchema = z14.enum(UPDATE_STATUSES);
11500
11500
  function currencyMinimums2(currencyCode) {
11501
11501
  return CURRENCY_MINIMUMS2[currencyCode] ?? DEFAULT_CURRENCY_MINIMUM2;
11502
11502
  }
11503
- function validateDailyBudgetFloor(money, ctx, path23) {
11503
+ function validateDailyBudgetFloor(money, ctx, path27) {
11504
11504
  if (money?.currencyCode) {
11505
11505
  const min = currencyMinimums2(money.currencyCode).dailyBudgetMin;
11506
11506
  if (Number(money.amount) < min) {
11507
- ctx.addIssue({ code: "custom", path: path23, message: `below the ${min} ${money.currencyCode} daily minimum` });
11507
+ ctx.addIssue({ code: "custom", path: path27, message: `below the ${min} ${money.currencyCode} daily minimum` });
11508
11508
  }
11509
11509
  }
11510
11510
  }
@@ -12136,19 +12136,19 @@ function failWriteValidation3(message) {
12136
12136
  writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
12137
12137
  process.exit(1);
12138
12138
  }
12139
- function loadJsonFileArg3(path23) {
12140
- if (typeof path23 !== "string" || path23.length === 0) {
12139
+ function loadJsonFileArg3(path27) {
12140
+ if (typeof path27 !== "string" || path27.length === 0) {
12141
12141
  return {};
12142
12142
  }
12143
12143
  try {
12144
- const parsed = JSON.parse(readFileSync8(path23, "utf8"));
12144
+ const parsed = JSON.parse(readFileSync8(path27, "utf8"));
12145
12145
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
12146
- failWriteValidation3(`${path23} must contain a JSON object`);
12146
+ failWriteValidation3(`${path27} must contain a JSON object`);
12147
12147
  }
12148
12148
  return parsed;
12149
12149
  } catch (err) {
12150
12150
  if (err instanceof SyntaxError) {
12151
- failWriteValidation3(`${path23} is not valid JSON: ${err.message}`);
12151
+ failWriteValidation3(`${path27} is not valid JSON: ${err.message}`);
12152
12152
  }
12153
12153
  throw err;
12154
12154
  }
@@ -16323,13 +16323,13 @@ function referenceRank(type) {
16323
16323
  function orderSlotsByIdentity(slots) {
16324
16324
  return [...slots].sort((a, b) => referenceRank(a.type) - referenceRank(b.type));
16325
16325
  }
16326
- function buildFramePrompt(edge, sceneIndex, framePrompt, present, hasAnchor, mode, sceneStyle, imageModel) {
16326
+ function buildFramePrompt(edge, sceneIndex, framePrompt, present2, hasAnchor, mode, sceneStyle, imageModel) {
16327
16327
  const profile = imageModel ? imageProfileFor(imageModel) : void 0;
16328
16328
  const exclusionsLast = profile?.constraintPlacement === "last";
16329
16329
  const photoreal = profile ? profile.photorealCue : true;
16330
16330
  const EDGE = edge.toUpperCase();
16331
16331
  const legend = [
16332
- ...present.map((s) => `- ${s.label} \u2014 ${roleForSlot(s)}`),
16332
+ ...present2.map((s) => `- ${s.label} \u2014 ${roleForSlot(s)}`),
16333
16333
  ...hasAnchor ? [
16334
16334
  "- 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."
16335
16335
  ] : []
@@ -16387,7 +16387,7 @@ function buildFramePrompt(edge, sceneIndex, framePrompt, present, hasAnchor, mod
16387
16387
  // RECAST is the whole point of a transform: the dropped el_* images define who/
16388
16388
  // what is on screen, NOT the source footage and NOT the prose. Without this, the
16389
16389
  // model reproduces the original ad's people (a proven failure mode).
16390
- ...present.length > 0 ? [
16390
+ ...present2.length > 0 ? [
16391
16391
  "IDENTITY & AESTHETIC \u2014 RECAST (this is a transform, not a copy):",
16392
16392
  "Identity comes from the reference image, never from the source footage or this prose. Render every",
16393
16393
  "person, animal, product, and set to MATCH its labeled reference image above \u2014 that image is the ONLY",
@@ -16442,17 +16442,17 @@ function ingestFrameRef(url, edge, ctx, nodes) {
16442
16442
  ctx.ingestCache?.set(url, ref);
16443
16443
  return ref;
16444
16444
  }
16445
- function buildFrameRef(edge, url, framePrompt, present, ctx, nodes) {
16445
+ function buildFrameRef(edge, url, framePrompt, present2, ctx, nodes) {
16446
16446
  const tag = ctx.tag ?? "";
16447
16447
  if (ctx.reuse && url) return ingestFrameRef(url, edge, ctx, nodes);
16448
- const castSlots = present.filter((s) => {
16448
+ const castSlots = present2.filter((s) => {
16449
16449
  const t = s.type.toLowerCase();
16450
16450
  return t === "person" || t === "animal";
16451
16451
  });
16452
16452
  const useOriginalAnchor = Boolean(url) && castSlots.length === 0;
16453
16453
  const hasOriginal = useOriginalAnchor;
16454
16454
  const originalRef = useOriginalAnchor && url ? ingestFrameRef(url, edge, ctx, nodes) : void 0;
16455
- const presentOrdered = orderSlotsByIdentity(present);
16455
+ const presentOrdered = orderSlotsByIdentity(present2);
16456
16456
  const reference = [...presentOrdered.map((s) => s.ref), ...originalRef ? [originalRef] : []];
16457
16457
  const imageProfile = imageProfileFor(ctx.imageModel);
16458
16458
  const genParams = {
@@ -16588,13 +16588,13 @@ function spokenTextParts(scene, nativeLine, loc, profile) {
16588
16588
  if (transcript) parts.push(`Transcript: ${loc(transcript)}`);
16589
16589
  return parts;
16590
16590
  }
16591
- function buildSeedancePrompt(scene, sceneIndex, present, mode, audio, nativeLine, nativeLang, uiRouted, videoModel) {
16591
+ function buildSeedancePrompt(scene, sceneIndex, present2, mode, audio, nativeLine, nativeLang, uiRouted, videoModel) {
16592
16592
  const profile = (videoModel ? clipProfileFor(videoModel) : void 0) ?? SEEDANCE_PROFILE;
16593
16593
  const loc = (s) => nativeLine ? localizeNumeralsForNative(s, nativeLang) : s;
16594
16594
  const routed = uiRouted ?? (compositeRegionsOf(scene) !== null && isUiOnlyComposite(compositeRegionsOf(scene) ?? []));
16595
16595
  const plate = routed ? compositePlateRegion(scene) ?? {} : null;
16596
16596
  const parts = visualBriefParts(scene, sceneIndex, plate);
16597
- const refs = plate ? present.filter((s) => !UI_SURFACE_RE.test(s.description ?? "")) : present;
16597
+ const refs = plate ? present2.filter((s) => !UI_SURFACE_RE.test(s.description ?? "")) : present2;
16598
16598
  if (refs.length > 0) {
16599
16599
  parts.push(
16600
16600
  `Keep these consistent with their references: ${refs.map((s) => `${s.label} (${s.description ?? s.type})`).join("; ")}`
@@ -16633,15 +16633,15 @@ function sceneOutTransition(scene, isLast) {
16633
16633
  const xfade = type ? XFADE_BY_TYPE[type] : void 0;
16634
16634
  return xfade ? { xfade, dur: TRANSITION_DEFAULT_S } : null;
16635
16635
  }
16636
- function sceneShootMode(scene, present, nativeTurn, cameraOn, casts) {
16636
+ function sceneShootMode(scene, present2, nativeTurn, cameraOn, casts) {
16637
16637
  const talking = Boolean(nativeTurn) || cameraOn && (scene.dialogue ?? []).some(
16638
16638
  (d) => d.line?.trim() && isOnCameraSpeaker(d.speaker ?? "voiceover", casts, cameraOn)
16639
16639
  );
16640
16640
  return deriveShootMode({
16641
16641
  explicit: scene.shoot_mode,
16642
16642
  talking,
16643
- hasPerson: present.some((s) => s.type.toLowerCase() === "person" || s.type.toLowerCase() === "animal"),
16644
- hasProduct: present.some((s) => s.type.toLowerCase() === "product")
16643
+ hasPerson: present2.some((s) => s.type.toLowerCase() === "person" || s.type.toLowerCase() === "animal"),
16644
+ hasProduct: present2.some((s) => s.type.toLowerCase() === "product")
16645
16645
  });
16646
16646
  }
16647
16647
  function emitSceneNativeAudio(i, nativeTurn, ambientBroll, lengths, clock, nodes, voTracks, nativeSegments, clipRef) {
@@ -16685,12 +16685,12 @@ function sameSpeakerTrackEntries(segments) {
16685
16685
  return sorted.map((s, k) => {
16686
16686
  const next = sorted[k + 1];
16687
16687
  const naturalLen = s.end_s - s.start_s;
16688
- const cap2 = next ? Math.min(s.end_s, next.start_s) - s.start_s : naturalLen;
16689
- const capped = cap2 < naturalLen - 1e-3;
16688
+ const cap3 = next ? Math.min(s.end_s, next.start_s) - s.start_s : naturalLen;
16689
+ const capped = cap3 < naturalLen - 1e-3;
16690
16690
  return {
16691
16691
  slot: `seg${k}`,
16692
16692
  start_s: s.start_s,
16693
- ...capped ? { duration_s: Math.round(Math.max(0.1, cap2) * 1e3) / 1e3 } : {}
16693
+ ...capped ? { duration_s: Math.round(Math.max(0.1, cap3) * 1e3) / 1e3 } : {}
16694
16694
  };
16695
16695
  });
16696
16696
  }
@@ -16729,7 +16729,7 @@ function buildPerSpeakerVoiceConversion(segments, totalMs, nodes) {
16729
16729
  }
16730
16730
  return tracks;
16731
16731
  }
16732
- function emitSceneClip(i, scene, present, mode, nativeTurn, ambientBroll, frames, lengths, out, opts, nodes, tag = "", defer = false) {
16732
+ function emitSceneClip(i, scene, present2, mode, nativeTurn, ambientBroll, frames, lengths, out, opts, nodes, tag = "", defer = false) {
16733
16733
  const profile = clipProfileFor(opts.videoModel) ?? SEEDANCE_PROFILE;
16734
16734
  const clipParams = {
16735
16735
  model: opts.videoModel,
@@ -16739,7 +16739,7 @@ function emitSceneClip(i, scene, present, mode, nativeTurn, ambientBroll, frames
16739
16739
  prompt: buildSeedancePrompt(
16740
16740
  scene,
16741
16741
  i,
16742
- present,
16742
+ present2,
16743
16743
  mode,
16744
16744
  Boolean(nativeTurn) || ambientBroll,
16745
16745
  nativeTurn?.text,
@@ -16838,9 +16838,9 @@ function uiRoutedRuns(input) {
16838
16838
  flushRun();
16839
16839
  return runs;
16840
16840
  }
16841
- function sceneIsFullScreenUi(scene, present) {
16841
+ function sceneIsFullScreenUi(scene, present2) {
16842
16842
  if (scene.narrative_role?.trim() === "cta") return false;
16843
- const hasCast = present.some((s) => {
16843
+ const hasCast = present2.some((s) => {
16844
16844
  const t = s.type.toLowerCase();
16845
16845
  return t === "person" || t === "animal";
16846
16846
  });
@@ -16875,14 +16875,14 @@ function presenterIndexOf(regions, hasNative) {
16875
16875
  if (flagged >= 0) return flagged;
16876
16876
  return hasNative ? 0 : -1;
16877
16877
  }
16878
- function slotsForRegion(present, isPresenter) {
16879
- return present.filter((s) => {
16878
+ function slotsForRegion(present2, isPresenter) {
16879
+ return present2.filter((s) => {
16880
16880
  const t = s.type.toLowerCase();
16881
16881
  const person = t === "person" || t === "animal";
16882
16882
  return isPresenter ? person : !person;
16883
16883
  });
16884
16884
  }
16885
- function buildCompositeScene(layout, regions, comp, scene, i, present, mode, nativeTurn, lengths, out, opts, nodes) {
16885
+ function buildCompositeScene(layout, regions, comp, scene, i, present2, mode, nativeTurn, lengths, out, opts, nodes) {
16886
16886
  const dims = canvasDims(opts.outAr);
16887
16887
  const presIdx = presenterIndexOf(regions, Boolean(nativeTurn));
16888
16888
  const regionRefs = [];
@@ -16890,7 +16890,7 @@ function buildCompositeScene(layout, regions, comp, scene, i, present, mode, nat
16890
16890
  regions.forEach((region, r) => {
16891
16891
  const isPresenter = r === presIdx;
16892
16892
  const tag = `_r${r}`;
16893
- const regionSlots = slotsForRegion(present, isPresenter);
16893
+ const regionSlots = slotsForRegion(present2, isPresenter);
16894
16894
  const ctx = {
16895
16895
  sceneIndex: i,
16896
16896
  genAr: opts.genAr,
@@ -16990,14 +16990,14 @@ function sceneTiming(scene, isLast, nativeTurn, durationSet = SEEDANCE_DURATIONS
16990
16990
  const genDur = ceilToDurations(durationSet, Math.max(trimTarget, speech));
16991
16991
  return { dur, out, trimTarget, genDur, speech };
16992
16992
  }
16993
- function emitCompositeScene(composite, scene, i, present, mode, nativeTurn, lengths, out, opts, clock, nodes, voTracks, nativeSegments, clips) {
16993
+ function emitCompositeScene(composite, scene, i, present2, mode, nativeTurn, lengths, out, opts, clock, nodes, voTracks, nativeSegments, clips) {
16994
16994
  const built = buildCompositeScene(
16995
16995
  composite.layout,
16996
16996
  composite.regions,
16997
16997
  composite.comp,
16998
16998
  scene,
16999
16999
  i,
17000
- present,
17000
+ present2,
17001
17001
  mode,
17002
17002
  nativeTurn,
17003
17003
  { dur: lengths.dur, trimTarget: lengths.trimTarget, genDur: lengths.genDur },
@@ -17102,9 +17102,9 @@ function emitGraphicScene(i, scene, lengths, out, surfaceIngests, nodes, clips)
17102
17102
  clips.push({ ref: `$ref:${refId}.asset`, scene_s: lengths.dur, out, still: { fit: "pad" } });
17103
17103
  }
17104
17104
  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;
17105
- function sceneIsBrandCard(scene, present, isCta) {
17105
+ function sceneIsBrandCard(scene, present2, isCta) {
17106
17106
  if (!isCta) return false;
17107
- const hasCast = present.some((s) => {
17107
+ const hasCast = present2.some((s) => {
17108
17108
  const t = s.type.toLowerCase();
17109
17109
  return t === "person" || t === "animal";
17110
17110
  });
@@ -17440,7 +17440,7 @@ function emitPhraseClip(phrase, voiceNode, env, nodes, out) {
17440
17440
  const anchor = phrase.shownScenes[0];
17441
17441
  const anchorScene = env.blueprint.scenes[anchor];
17442
17442
  if (!anchorScene) return;
17443
- const present = slotsForScene(env.slots, anchor);
17443
+ const present2 = slotsForScene(env.slots, anchor);
17444
17444
  const nativeTurn = {
17445
17445
  sceneIndex: anchor,
17446
17446
  speaker: phrase.speaker,
@@ -17450,7 +17450,7 @@ function emitPhraseClip(phrase, voiceNode, env, nodes, out) {
17450
17450
  voiceNode,
17451
17451
  native: true
17452
17452
  };
17453
- const mode = sceneShootMode(anchorScene, present, nativeTurn, env.cameraOn, env.casts);
17453
+ const mode = sceneShootMode(anchorScene, present2, nativeTurn, env.cameraOn, env.casts);
17454
17454
  const ctx = {
17455
17455
  sceneIndex: anchor,
17456
17456
  genAr: env.genAr,
@@ -17493,7 +17493,7 @@ function emitPhraseClip(phrase, voiceNode, env, nodes, out) {
17493
17493
  prompt: buildSeedancePrompt(
17494
17494
  anchorScene,
17495
17495
  anchor,
17496
- present,
17496
+ present2,
17497
17497
  mode,
17498
17498
  true,
17499
17499
  phrase.text,
@@ -17610,7 +17610,7 @@ function emitPhraseTts(phrase, voiceNode, idx, used, clock, nodes, out, language
17610
17610
  });
17611
17611
  }
17612
17612
  function emitCompositeInTimeline(composite, scene, i, isLast, env, canonical, ensureVoiceNode, usedVoIds, nodes, out) {
17613
- const present = slotsForScene(env.slots, i);
17613
+ const present2 = slotsForScene(env.slots, i);
17614
17614
  const onCam = (scene.dialogue ?? []).filter(
17615
17615
  (l) => Boolean(l.line?.trim()) && isOnCameraSpeaker(l.speaker ?? "voiceover", env.casts, env.cameraOn)
17616
17616
  );
@@ -17631,13 +17631,13 @@ function emitCompositeInTimeline(composite, scene, i, isLast, env, canonical, en
17631
17631
  speech_words: wordCount(text)
17632
17632
  });
17633
17633
  }
17634
- const mode = sceneShootMode(scene, present, nativeTurn, env.cameraOn, env.casts);
17634
+ const mode = sceneShootMode(scene, present2, nativeTurn, env.cameraOn, env.casts);
17635
17635
  const lengths = sceneTiming(scene, isLast, nativeTurn, clipProfileFor(env.opts.videoModel)?.durationSet);
17636
17636
  emitCompositeScene(
17637
17637
  composite,
17638
17638
  scene,
17639
17639
  i,
17640
- present,
17640
+ present2,
17641
17641
  mode,
17642
17642
  nativeTurn,
17643
17643
  lengths,
@@ -17732,8 +17732,8 @@ function brollKeyframes(scene, i, env, ctx, lengths, prevEndFrame, nodes) {
17732
17732
  return { first, last, sharesPrevFrame };
17733
17733
  }
17734
17734
  function emitBrollScene(scene, i, isLast, env, nodes, out, prevEndFrame) {
17735
- const present = slotsForScene(env.slots, i);
17736
- const mode = sceneShootMode(scene, present, void 0, env.cameraOn, env.casts);
17735
+ const present2 = slotsForScene(env.slots, i);
17736
+ const mode = sceneShootMode(scene, present2, void 0, env.cameraOn, env.casts);
17737
17737
  const ambientBroll = Boolean(env.opts.ambient) && mode !== "ugc_selfie";
17738
17738
  const lengths = sceneTiming(scene, isLast, void 0, clipProfileFor(env.opts.videoModel)?.durationSet);
17739
17739
  const ctx = {
@@ -17750,13 +17750,13 @@ function emitBrollScene(scene, i, isLast, env, nodes, out, prevEndFrame) {
17750
17750
  out.routes.set(i, "graphic_plate");
17751
17751
  return void 0;
17752
17752
  }
17753
- if (!env.reuse && sceneIsFullScreenUi(scene, present)) {
17753
+ if (!env.reuse && sceneIsFullScreenUi(scene, present2)) {
17754
17754
  emitScreenScene(i, scene, lengths, lengths.out, env.surfaceIngests, nodes, out.clips);
17755
17755
  out.routes.set(i, "screen_still");
17756
17756
  return void 0;
17757
17757
  }
17758
17758
  const isCta = scene.narrative_role?.trim() === "cta" || isLast;
17759
- if (!env.reuse && sceneIsBrandCard(scene, present, isCta)) {
17759
+ if (!env.reuse && sceneIsBrandCard(scene, present2, isCta)) {
17760
17760
  emitBrandCardScene(i, lengths, lengths.out, env.outAr, brandPlateColor(env.blueprint), nodes, out.clips);
17761
17761
  out.routes.set(i, "brand_card");
17762
17762
  return void 0;
@@ -17771,7 +17771,7 @@ function emitBrollScene(scene, i, isLast, env, nodes, out, prevEndFrame) {
17771
17771
  const clip = emitSceneClip(
17772
17772
  i,
17773
17773
  scene,
17774
- present,
17774
+ present2,
17775
17775
  mode,
17776
17776
  void 0,
17777
17777
  ambientBroll,
@@ -18533,7 +18533,7 @@ function sceneSpokenText(scene) {
18533
18533
  return (scene.dialogue ?? []).map((d) => d.line?.trim()).filter((l) => Boolean(l)).join(" ") || null;
18534
18534
  }
18535
18535
  function buildMotionBoard(blueprint) {
18536
- const round4 = (n) => Math.round(n * 100) / 100;
18536
+ const round5 = (n) => Math.round(n * 100) / 100;
18537
18537
  let cursor = 0;
18538
18538
  return blueprint.scenes.map((scene, i) => {
18539
18539
  const start_s = scene.start_s ?? cursor;
@@ -18545,15 +18545,15 @@ function buildMotionBoard(blueprint) {
18545
18545
  const graphics = [
18546
18546
  ...(overlays.success ? overlays.data : []).filter((ov) => ov.text?.trim()).map((ov) => ({
18547
18547
  kind: "text",
18548
- at_s: round4(ov.appears_at_s ?? start_s),
18549
- dur_s: round4(ov.duration_s ?? 2.5),
18548
+ at_s: round5(ov.appears_at_s ?? start_s),
18549
+ dur_s: round5(ov.duration_s ?? 2.5),
18550
18550
  position: ov.position ?? "bottom_center",
18551
18551
  text: ov.text?.trim()
18552
18552
  })),
18553
18553
  ...(floats.success ? floats.data : []).map((fe) => ({
18554
18554
  kind: "graphic",
18555
- at_s: round4(fe.appears_at_s ?? start_s),
18556
- dur_s: round4(fe.duration_s ?? 2.5),
18555
+ at_s: round5(fe.appears_at_s ?? start_s),
18556
+ dur_s: round5(fe.duration_s ?? 2.5),
18557
18557
  position: fe.position ?? "bottom_center",
18558
18558
  label: fe.brand_name || fe.what_it_represents || fe.description || fe.kind || "element"
18559
18559
  }))
@@ -18561,7 +18561,7 @@ function buildMotionBoard(blueprint) {
18561
18561
  return {
18562
18562
  scene: i,
18563
18563
  role: resolveSceneRole(scene, i, blueprint.scenes.length),
18564
- window_s: [round4(start_s), round4(end_s)],
18564
+ window_s: [round5(start_s), round5(end_s)],
18565
18565
  // A continuation b-roll scene shares the previous scene's end frame as its start
18566
18566
  // (no own `s<i>_start` node), so point the storyboard at that shared keyframe.
18567
18567
  storyboard_frames: [scene.continues_previous && i > 0 ? `s${i - 1}_end` : `s${i}_start`],
@@ -19226,10 +19226,10 @@ function runDirsToPrune(entries, keep, currentRunId) {
19226
19226
  return runs.slice(0, Math.max(0, runs.length - keep));
19227
19227
  }
19228
19228
  async function pruneOldRuns(outputsDir, keep, currentRunId, log) {
19229
- const { readdir: readdir7 } = await import("fs/promises");
19229
+ const { readdir: readdir9 } = await import("fs/promises");
19230
19230
  let entries;
19231
19231
  try {
19232
- entries = await readdir7(outputsDir);
19232
+ entries = await readdir9(outputsDir);
19233
19233
  } catch {
19234
19234
  return;
19235
19235
  }
@@ -21001,10 +21001,10 @@ function resolveShippedCanvasDir(name, startDir, exists = existsSync3, maxDepth
21001
21001
  import { appendFile, readFile as readFile13 } from "fs/promises";
21002
21002
  import path19 from "path";
21003
21003
  function missingGitignoreEntries(existing, entries) {
21004
- const present = new Set(
21004
+ const present2 = new Set(
21005
21005
  existing.split("\n").map((l) => l.trim().replace(/\/+$/, "")).filter((l) => l.length > 0 && !l.startsWith("#"))
21006
21006
  );
21007
- return entries.filter((e) => !present.has(e.trim().replace(/\/+$/, "")));
21007
+ return entries.filter((e) => !present2.has(e.trim().replace(/\/+$/, "")));
21008
21008
  }
21009
21009
  async function ensureGitignore(dir, entries) {
21010
21010
  const file = path19.join(dir, ".gitignore");
@@ -22279,12 +22279,12 @@ function listFlowSlugs() {
22279
22279
  return readdirSync2(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith("_") && entry.name !== ".gitkeep").map((entry) => entry.name).sort();
22280
22280
  }
22281
22281
  function readFlowTree(slug) {
22282
- const path23 = join3(flowsDir(), slug, "_data.json");
22283
- if (!existsSync4(path23)) {
22282
+ const path27 = join3(flowsDir(), slug, "_data.json");
22283
+ if (!existsSync4(path27)) {
22284
22284
  failLocal(`No form "${slug}". Run "baker flows list" to see the forms in this workspace.`);
22285
22285
  }
22286
22286
  try {
22287
- return JSON.parse(readFileSync9(path23, "utf-8"));
22287
+ return JSON.parse(readFileSync9(path27, "utf-8"));
22288
22288
  } catch (error) {
22289
22289
  failLocal(`Could not read form "${slug}": ${error instanceof Error ? error.message : String(error)}`);
22290
22290
  }
@@ -23444,9 +23444,9 @@ async function readImageBuffer(pathOrUrl) {
23444
23444
  }
23445
23445
  return readFile18(pathOrUrl);
23446
23446
  }
23447
- async function isDirectory(path23) {
23447
+ async function isDirectory(path27) {
23448
23448
  try {
23449
- const s = await stat4(path23);
23449
+ const s = await stat4(path27);
23450
23450
  return s.isDirectory();
23451
23451
  } catch {
23452
23452
  return false;
@@ -25146,7 +25146,7 @@ function emitError3(err) {
25146
25146
  process.exit(1);
25147
25147
  }
25148
25148
  function coerceRawArgs(args) {
25149
- const num = (v) => {
25149
+ const num2 = (v) => {
25150
25150
  if (typeof v === "number") return v;
25151
25151
  if (typeof v === "string" && v.length > 0) {
25152
25152
  const n = Number(v);
@@ -25161,8 +25161,8 @@ function coerceRawArgs(args) {
25161
25161
  color: str(args.color),
25162
25162
  "remove-bg": bool(args["remove-bg"]),
25163
25163
  "shrink-to-content": bool(args["shrink-to-content"]),
25164
- height: num(args.height),
25165
- width: num(args.width),
25164
+ height: num2(args.height),
25165
+ width: num2(args.width),
25166
25166
  size: str(args.size),
25167
25167
  fit: str(args.fit),
25168
25168
  output: str(args.output),
@@ -26010,8 +26010,1182 @@ Full guide: __tooling__/docs/tools/baker/images.md`
26010
26010
  }
26011
26011
  });
26012
26012
 
26013
- // src/commands/mcp/index.ts
26013
+ // src/commands/landing/index.ts
26014
+ import { defineCommand as defineCommand134 } from "citty";
26015
+
26016
+ // src/commands/landing/critique.ts
26017
+ import { readdir as readdir8, stat as stat6 } from "fs/promises";
26018
+ import path26 from "path";
26014
26019
  import { defineCommand as defineCommand133 } from "citty";
26020
+
26021
+ // src/engine/landing/lib/brand-tokens.ts
26022
+ import { readFile as readFile20 } from "fs/promises";
26023
+ import path23 from "path";
26024
+
26025
+ // src/engine/landing/lib/color.ts
26026
+ var NEUTRAL_COLOR_KEYWORDS = /* @__PURE__ */ new Set([
26027
+ "transparent",
26028
+ "currentcolor",
26029
+ "black",
26030
+ "white",
26031
+ "gray",
26032
+ "grey",
26033
+ "silver",
26034
+ "dimgray",
26035
+ "dimgrey",
26036
+ "darkgray",
26037
+ "darkgrey",
26038
+ "lightgray",
26039
+ "lightgrey",
26040
+ "gainsboro",
26041
+ "whitesmoke"
26042
+ ]);
26043
+ function hexChannels(color) {
26044
+ const long = color.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})(?:[0-9a-f]{2})?$/i);
26045
+ if (long)
26046
+ return [
26047
+ Number.parseInt(long[1] ?? "0", 16),
26048
+ Number.parseInt(long[2] ?? "0", 16),
26049
+ Number.parseInt(long[3] ?? "0", 16)
26050
+ ];
26051
+ const short = color.match(/^#([0-9a-f])([0-9a-f])([0-9a-f])(?:[0-9a-f])?$/i);
26052
+ if (short) {
26053
+ const dup = (s) => Number.parseInt(`${s ?? "0"}${s ?? "0"}`, 16);
26054
+ return [dup(short[1]), dup(short[2]), dup(short[3])];
26055
+ }
26056
+ return null;
26057
+ }
26058
+ function parseColor2(raw) {
26059
+ const c = String(raw || "").trim().toLowerCase();
26060
+ if (!c || c === "transparent") return null;
26061
+ const rgb = c.match(/rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)(?:[\s,/]+([\d.]+%?))?\s*\)/i);
26062
+ if (rgb) {
26063
+ const alpha = rgb[4];
26064
+ const a = alpha === void 0 ? 1 : alpha.endsWith("%") ? Number.parseFloat(alpha) / 100 : Number.parseFloat(alpha);
26065
+ return { r: Number(rgb[1]), g: Number(rgb[2]), b: Number(rgb[3]), a };
26066
+ }
26067
+ const oklch = parseOklch(c);
26068
+ if (oklch) return oklch;
26069
+ const hex = hexChannels(c);
26070
+ if (hex) return { r: hex[0], g: hex[1], b: hex[2], a: 1 };
26071
+ return null;
26072
+ }
26073
+ function parseOklch(c) {
26074
+ const m = c.match(/oklch\(\s*([\d.]+%?)\s+([\d.]+%?)\s+([\d.]+)(?:deg)?(?:\s*\/\s*([\d.]+%?))?\s*\)/i);
26075
+ if (!m) return null;
26076
+ const pct = (s, scale) => s.endsWith("%") ? Number.parseFloat(s) / 100 * scale : Number.parseFloat(s);
26077
+ const alpha = m[4];
26078
+ const a = alpha === void 0 ? 1 : pct(alpha, 1);
26079
+ return { ...oklchToRgb(pct(m[1] ?? "0", 1), pct(m[2] ?? "0", 0.4), Number.parseFloat(m[3] ?? "0")), a };
26080
+ }
26081
+ function oklchToRgb(L, C, H) {
26082
+ const hRad = H * Math.PI / 180;
26083
+ const a = C * Math.cos(hRad);
26084
+ const b = C * Math.sin(hRad);
26085
+ const l = (L + 0.3963377774 * a + 0.2158037573 * b) ** 3;
26086
+ const m = (L - 0.1055613458 * a - 0.0638541728 * b) ** 3;
26087
+ const s = (L - 0.0894841775 * a - 1.291485548 * b) ** 3;
26088
+ const lin = [
26089
+ 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
26090
+ -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
26091
+ -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s
26092
+ ].map((v) => {
26093
+ const g = v <= 31308e-7 ? 12.92 * v : 1.055 * Math.sign(v) * Math.abs(v) ** (1 / 2.4) - 0.055;
26094
+ return Math.round(Math.max(0, Math.min(1, g)) * 255);
26095
+ });
26096
+ return { r: lin[0] ?? 0, g: lin[1] ?? 0, b: lin[2] ?? 0 };
26097
+ }
26098
+ function isNeutralAuthoredColor(rawColor) {
26099
+ const c = String(rawColor || "").trim().toLowerCase();
26100
+ if (!c) return false;
26101
+ if (NEUTRAL_COLOR_KEYWORDS.has(c)) return true;
26102
+ if (/^rgba?\(/i.test(c)) {
26103
+ const channels2 = c.match(/^rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)/i);
26104
+ if (channels2) {
26105
+ const values = [1, 2, 3].map((i) => Number(channels2[i]));
26106
+ return Math.max(...values) - Math.min(...values) < 30;
26107
+ }
26108
+ return false;
26109
+ }
26110
+ const oklch = c.match(/oklch\(\s*[\d.]+%?\s+([\d.-]+)/i);
26111
+ if (oklch) return Number.parseFloat(oklch[1] ?? "0") < 0.02;
26112
+ const lch = c.match(/lch\(\s*[\d.]+%?\s+([\d.-]+)/i);
26113
+ if (lch) return Number.parseFloat(lch[1] ?? "0") < 3;
26114
+ const hsl = c.match(/hsla?\(\s*[\d.-]+\s*,?\s*([\d.]+)%/i);
26115
+ if (hsl) return Number.parseFloat(hsl[1] ?? "0") < 10;
26116
+ const channels = hexChannels(c);
26117
+ if (channels) return Math.max(...channels) - Math.min(...channels) < 30;
26118
+ return false;
26119
+ }
26120
+ function hasChroma(c, threshold = 30) {
26121
+ if (!c) return false;
26122
+ return Math.max(c.r, c.g, c.b) - Math.min(c.r, c.g, c.b) >= threshold;
26123
+ }
26124
+ function getHue(c) {
26125
+ if (!c) return 0;
26126
+ const r = c.r / 255;
26127
+ const g = c.g / 255;
26128
+ const b = c.b / 255;
26129
+ const max = Math.max(r, g, b);
26130
+ const min = Math.min(r, g, b);
26131
+ if (max === min) return 0;
26132
+ const d = max - min;
26133
+ let h;
26134
+ if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
26135
+ else if (max === g) h = ((b - r) / d + 2) / 6;
26136
+ else h = ((r - g) / d + 4) / 6;
26137
+ return Math.round(h * 360);
26138
+ }
26139
+ function isAiPurpleHue(hue) {
26140
+ return hue >= 225 && hue <= 310;
26141
+ }
26142
+ function isCreamColor(c) {
26143
+ if (Math.min(c.r, c.g, c.b) < 209) return false;
26144
+ if (!(c.r >= c.g && c.g >= c.b)) return false;
26145
+ const warmth = c.r - c.b;
26146
+ return warmth >= 6 && warmth <= 48;
26147
+ }
26148
+
26149
+ // src/engine/landing/lib/brand-tokens.ts
26150
+ var EMPTY = { fonts: /* @__PURE__ */ new Set(), colors: [], hasTokens: false };
26151
+ function parseBrandTokens(globalCss, brandMd) {
26152
+ const fonts = /* @__PURE__ */ new Set();
26153
+ const colors = [];
26154
+ parseGlobalCss(globalCss, fonts, colors);
26155
+ parseBrandMd(brandMd, fonts, colors);
26156
+ return { fonts, colors, hasTokens: fonts.size > 0 || colors.length > 0 };
26157
+ }
26158
+ function parseGlobalCss(rawCss, fonts, colors) {
26159
+ const globalCss = rawCss.replace(/\/\*[\s\S]*?\*\//g, " ");
26160
+ for (const m of globalCss.matchAll(/--font-[\w-]*\s*:\s*([^;]+);/gi)) {
26161
+ const first = (m[1] ?? "").split(",")[0]?.trim().replace(/^['"]|['"]$/g, "").toLowerCase();
26162
+ if (first) fonts.add(first);
26163
+ }
26164
+ for (const m of globalCss.matchAll(
26165
+ /--color-[\w-]*\s*:\s*(#[0-9a-f]{3,8}\b|rgba?\([^)]+\)|oklch\([^)]+\)|hsla?\([^)]+\))/gi
26166
+ )) {
26167
+ const c = parseColor2(m[1] ?? "");
26168
+ if (c) colors.push(c);
26169
+ }
26170
+ }
26171
+ function parseBrandMd(brandMd, fonts, colors) {
26172
+ const isProhibition = (line) => /\b(?:never|don'?t|do not|avoid|not|instead of|ban(?:ned)?)\b/i.test(line);
26173
+ for (const line of brandMd.split("\n")) {
26174
+ if (isProhibition(line)) continue;
26175
+ for (const m of line.matchAll(/#[0-9a-f]{6}\b/gi)) {
26176
+ const c = parseColor2(m[0]);
26177
+ if (c) colors.push(c);
26178
+ }
26179
+ if (!/\b(?:font|typeface|family)\b/i.test(line)) continue;
26180
+ const named = line.match(/["'`]([A-Za-z][\w ]{1,30})["'`]/g);
26181
+ if (named) for (const n of named) fonts.add(n.slice(1, -1).trim().toLowerCase());
26182
+ }
26183
+ }
26184
+ async function loadBrandTokens(projectRoot) {
26185
+ const globalCss = await safeRead(path23.join(projectRoot, "src", "styles", "global.css"));
26186
+ const brandMd = await safeRead(path23.join(projectRoot, "src", "brand", "BRAND.md"));
26187
+ if (!globalCss && !brandMd) return EMPTY;
26188
+ return parseBrandTokens(globalCss, brandMd);
26189
+ }
26190
+ async function safeRead(file) {
26191
+ try {
26192
+ return await readFile20(file, "utf8");
26193
+ } catch {
26194
+ return "";
26195
+ }
26196
+ }
26197
+ function brandCommitsFont(brand, snippet) {
26198
+ if (brand.fonts.size === 0) return false;
26199
+ const lower = snippet.toLowerCase();
26200
+ for (const f of brand.fonts) if (f.length >= 3 && lower.includes(f)) return true;
26201
+ return false;
26202
+ }
26203
+ function brandCommitsAiHue(brand) {
26204
+ return brand.colors.some((c) => hasChroma(c, 40) && isAiPurpleHue(getHue(c)));
26205
+ }
26206
+ function brandCommitsCream(brand) {
26207
+ return brand.colors.some((c) => isCreamColor(c));
26208
+ }
26209
+
26210
+ // src/engine/landing/lib/constants.ts
26211
+ var OVERUSED_FONTS = /* @__PURE__ */ new Set([
26212
+ // Older monoculture (still ubiquitous):
26213
+ "inter",
26214
+ "roboto",
26215
+ "open sans",
26216
+ "lato",
26217
+ "montserrat",
26218
+ "arial",
26219
+ "helvetica",
26220
+ // Newer monoculture (the Anthropic-skill / Vercel / GitHub default wave):
26221
+ "fraunces",
26222
+ "instrument sans",
26223
+ "instrument serif",
26224
+ "geist",
26225
+ "geist sans",
26226
+ "geist mono",
26227
+ "mona sans",
26228
+ "plus jakarta sans",
26229
+ "space grotesk",
26230
+ "space mono",
26231
+ "recoleta",
26232
+ "dm sans",
26233
+ "dm serif display",
26234
+ "dm serif text",
26235
+ "outfit",
26236
+ "syne",
26237
+ "cormorant",
26238
+ "playfair display",
26239
+ "lora",
26240
+ "crimson",
26241
+ "newsreader",
26242
+ "ibm plex",
26243
+ "ibm plex sans",
26244
+ "ibm plex serif"
26245
+ ]);
26246
+ function isOverusedFont(family) {
26247
+ if (OVERUSED_FONTS.has(family)) return true;
26248
+ for (const entry of OVERUSED_FONTS) if (family.startsWith(`${entry} `)) return true;
26249
+ return false;
26250
+ }
26251
+ var GENERIC_FONTS = /* @__PURE__ */ new Set([
26252
+ "serif",
26253
+ "sans-serif",
26254
+ "monospace",
26255
+ "cursive",
26256
+ "fantasy",
26257
+ "system-ui",
26258
+ "ui-serif",
26259
+ "ui-sans-serif",
26260
+ "ui-monospace",
26261
+ "ui-rounded",
26262
+ "-apple-system",
26263
+ "blinkmacsystemfont",
26264
+ "segoe ui",
26265
+ "inherit",
26266
+ "initial",
26267
+ "unset",
26268
+ "revert"
26269
+ ]);
26270
+ var EM_DASH_FLOOR = 8;
26271
+ var EM_DASH_CHARS_PER_DASH = 500;
26272
+ var TAILWIND_TEXT_PX = {
26273
+ "text-xs": 12,
26274
+ "text-sm": 14,
26275
+ "text-base": 16,
26276
+ "text-lg": 18,
26277
+ "text-xl": 20,
26278
+ "text-2xl": 24,
26279
+ "text-3xl": 30,
26280
+ "text-4xl": 36,
26281
+ "text-5xl": 48,
26282
+ "text-6xl": 60,
26283
+ "text-7xl": 72,
26284
+ "text-8xl": 96,
26285
+ "text-9xl": 128
26286
+ };
26287
+ var BUZZWORDS = [
26288
+ "streamline your",
26289
+ "empower your",
26290
+ "supercharge your",
26291
+ "unleash your",
26292
+ "unleash the power",
26293
+ "leverage the power",
26294
+ "built for the modern",
26295
+ "trusted by leading",
26296
+ "trusted by the world",
26297
+ "best-in-class",
26298
+ "industry-leading",
26299
+ "world-class",
26300
+ "enterprise-grade",
26301
+ "next-generation",
26302
+ "cutting-edge",
26303
+ "transform your business",
26304
+ "revolutionize",
26305
+ "game-changer",
26306
+ "game changing",
26307
+ "mission-critical",
26308
+ "best of breed",
26309
+ "future-proof",
26310
+ "future proof",
26311
+ "seamless experience",
26312
+ "seamlessly integrate",
26313
+ "drive engagement",
26314
+ "drive growth",
26315
+ "drive results",
26316
+ "harness the power"
26317
+ ];
26318
+ var AI_PURPLE_HEXES = /* @__PURE__ */ new Set([
26319
+ "#7c3aed",
26320
+ "#8b5cf6",
26321
+ "#a855f7",
26322
+ "#9333ea",
26323
+ "#7e22ce",
26324
+ "#6d28d9",
26325
+ "#6366f1",
26326
+ "#764ba2",
26327
+ "#667eea"
26328
+ ]);
26329
+ var CREAM_TAILWIND_BG = /* @__PURE__ */ new Set([
26330
+ "bg-amber-50",
26331
+ "bg-amber-100",
26332
+ "bg-orange-50",
26333
+ "bg-orange-100",
26334
+ "bg-yellow-50",
26335
+ "bg-stone-50",
26336
+ "bg-stone-100",
26337
+ "bg-stone-200"
26338
+ ]);
26339
+ var RULE_META = {
26340
+ "gradient-text": {
26341
+ family: "color",
26342
+ severity: "block",
26343
+ note: "Gradient text is a top AI tell. Emphasis comes from weight or size, not a clipped gradient fill."
26344
+ },
26345
+ "broken-image": {
26346
+ family: "integrity",
26347
+ severity: "block",
26348
+ note: "An <img> with an empty or placeholder src ships a broken image. Source a real asset via `baker images`."
26349
+ },
26350
+ "side-tab": {
26351
+ family: "borders_depth",
26352
+ severity: "warn",
26353
+ note: "A thick colored border on one edge is the 'side-tab' AI tell. Use full borders, a background, or spacing instead."
26354
+ },
26355
+ "border-accent-on-rounded": {
26356
+ family: "borders_depth",
26357
+ severity: "warn",
26358
+ note: "A colored top/bottom accent bar on a rounded card is an AI tell. Let the card's own surface carry the accent."
26359
+ },
26360
+ "overused-font": {
26361
+ family: "typography",
26362
+ severity: "warn",
26363
+ brandAware: "font",
26364
+ note: "This face is a training-data default. Pick a face with a point of view \u2014 unless BRAND.md commits it."
26365
+ },
26366
+ "single-font": {
26367
+ family: "typography",
26368
+ severity: "advisory",
26369
+ note: "Only one face across the page flattens hierarchy. A display/body pair usually reads richer."
26370
+ },
26371
+ "flat-type-hierarchy": {
26372
+ family: "typography",
26373
+ severity: "warn",
26374
+ note: "Type steps are too close (<2\xD7 span). Widen the scale so the primary, secondary, and body are obvious at a glance."
26375
+ },
26376
+ "ai-color-palette": {
26377
+ family: "color",
26378
+ severity: "warn",
26379
+ brandAware: "color",
26380
+ note: "Purple/indigo-on-heading or purple\u2192cyan gradients are the AI palette cluster. Commit to the brand's own hues."
26381
+ },
26382
+ "cream-palette": {
26383
+ family: "color",
26384
+ severity: "warn",
26385
+ brandAware: "color",
26386
+ note: "Cream/parchment ground is the 'measured rendition' AI default. Rework from the brand's saturated materials."
26387
+ },
26388
+ "gray-on-color": {
26389
+ family: "color",
26390
+ severity: "warn",
26391
+ note: "Gray text on a colored surface reads muddy. Tint secondary text from the surface hue, never neutral gray."
26392
+ },
26393
+ "bounce-easing": {
26394
+ family: "motion",
26395
+ severity: "warn",
26396
+ note: "Bounce/elastic easing feels dated. Use natural deceleration (e.g. cubic-bezier(0.16, 1, 0.3, 1))."
26397
+ },
26398
+ "layout-transition": {
26399
+ family: "motion",
26400
+ severity: "advisory",
26401
+ note: "Transitioning width/height/padding/margin janks. Animate transform/opacity, or clip-path/max-height with care."
26402
+ },
26403
+ "monotonous-spacing": {
26404
+ family: "spacing",
26405
+ severity: "warn",
26406
+ note: "One spacing value dominates. Create rhythm \u2014 tight groups, generous separation, more space above a heading than below."
26407
+ },
26408
+ "marketing-buzzword": {
26409
+ family: "copy",
26410
+ severity: "warn",
26411
+ note: "SaaS filler ('supercharge your\u2026') is generic. Name names, use numbers, say the specific thing only this product proves."
26412
+ },
26413
+ "aphoristic-cadence": {
26414
+ family: "copy",
26415
+ severity: "warn",
26416
+ note: "Manufactured-contrast cadence ('Not an X. A Y.') is an AI copy tell. Vary sentence shape; make a real claim."
26417
+ },
26418
+ "em-dash-overuse": {
26419
+ family: "copy",
26420
+ severity: "warn",
26421
+ note: "Dense em-dashes are an AI cadence tell (and brand copy bans them). Recast with commas, colons, or full stops."
26422
+ },
26423
+ "dark-glow": {
26424
+ family: "borders_depth",
26425
+ severity: "warn",
26426
+ note: "A zero-offset colored glow is decoration, not depth. Shadows carry an offset and a soft blur."
26427
+ },
26428
+ marquee: {
26429
+ family: "motion",
26430
+ severity: "warn",
26431
+ note: "Auto-scrolling marquees are a dated tell. Let the visitor control the pace, or drop the motion."
26432
+ }
26433
+ };
26434
+ var SEVERITY_WEIGHT = {
26435
+ block: 0.5,
26436
+ warn: 0.2,
26437
+ advisory: 0.05
26438
+ };
26439
+
26440
+ // src/engine/landing/lib/rules.ts
26441
+ var cap2 = (m, i) => m[i] ?? "";
26442
+ var num = (m, i) => Number(m[i] ?? 0);
26443
+ var hasRounded = (line) => /\brounded(?:-\w+)?\b/.test(line) || /border-radius/i.test(line);
26444
+ var isSafeElement = (line) => /<(?:blockquote|nav[\s>]|pre[\s>]|code[\s>]|a\s|input[\s>]|span[\s>])/i.test(line);
26445
+ var hasNeutralTailwindBorder = (line) => /\bborder-(?:gray|slate|zinc|neutral|stone|white|black|transparent|current|inherit)\b|\bborder-(?:gray|slate|zinc|neutral|stone)-\d+\b/.test(
26446
+ line
26447
+ );
26448
+ function isNeutralBorderColor(str) {
26449
+ const m = str.match(/solid\s+((?:rgba?|hsla?|oklch|oklab|lab|lch|hwb|color)\([^)]*\)|#[0-9a-f]{3,8}\b|[a-z]+)/i);
26450
+ if (!m) return false;
26451
+ return isNeutralAuthoredColor(m[1] ?? "");
26452
+ }
26453
+ var LINE_MATCHERS = [
26454
+ // ── Side-tab: a thick colored border on one edge ──────────────────────────
26455
+ {
26456
+ id: "side-tab",
26457
+ regex: /\bborder-[lrse]-(\d+)\b/g,
26458
+ test: (m, line) => {
26459
+ if (isSafeElement(line) || hasNeutralTailwindBorder(line)) return false;
26460
+ return hasRounded(line) ? num(m, 1) >= 2 : num(m, 1) >= 4;
26461
+ },
26462
+ fmt: (m) => cap2(m, 0)
26463
+ },
26464
+ {
26465
+ id: "side-tab",
26466
+ regex: /border-(?:left|right)\s*:\s*(\d+)px\s+solid[^;]*/gi,
26467
+ test: (m, line) => {
26468
+ if (isSafeElement(line)) return false;
26469
+ if (isNeutralBorderColor(cap2(m, 0))) return false;
26470
+ return hasRounded(line) ? num(m, 1) >= 2 : num(m, 1) >= 3;
26471
+ },
26472
+ fmt: (m) => cap2(m, 0).replace(/\s*;?\s*$/, "")
26473
+ },
26474
+ {
26475
+ id: "side-tab",
26476
+ regex: /border-(?:left|right)-width\s*:\s*(\d+)px/gi,
26477
+ test: (m, line) => !isSafeElement(line) && num(m, 1) >= 3,
26478
+ fmt: (m) => cap2(m, 0)
26479
+ },
26480
+ {
26481
+ id: "side-tab",
26482
+ regex: /border-inline-(?:start|end)(?:-width)?\s*:\s*(\d+)px/gi,
26483
+ test: (m, line) => !isSafeElement(line) && num(m, 1) >= 3,
26484
+ fmt: (m) => cap2(m, 0)
26485
+ },
26486
+ // ── Border accent on a rounded card (top/bottom colored bar) ──────────────
26487
+ {
26488
+ id: "border-accent-on-rounded",
26489
+ regex: /\bborder-[tb]-(\d+)\b/g,
26490
+ test: (m, line) => hasRounded(line) && num(m, 1) >= 1 && !hasNeutralTailwindBorder(line),
26491
+ fmt: (m) => cap2(m, 0)
26492
+ },
26493
+ {
26494
+ id: "border-accent-on-rounded",
26495
+ regex: /border-(?:top|bottom)\s*:\s*(\d+)px\s+solid/gi,
26496
+ test: (m, line) => num(m, 1) >= 3 && hasRounded(line),
26497
+ fmt: (m) => cap2(m, 0)
26498
+ },
26499
+ // ── Gradient text (Tailwind class form; authored-CSS form is the window
26500
+ // analyzer below, so a multi-line rule block is still caught) ──────────────
26501
+ {
26502
+ id: "gradient-text",
26503
+ regex: /\bbg-clip-text\b/g,
26504
+ test: (_m, line) => (
26505
+ // v3 `bg-gradient-to-*`, v4 `bg-linear-to-*` / `bg-radial*` / `bg-conic*`,
26506
+ // and arbitrary-value `bg-[linear-gradient(…)]` spellings.
26507
+ /\bbg-gradient-to-|\bbg-linear-to-|\bbg-(?:radial|conic)\b|\bbg-\[(?:linear|radial|conic)-gradient/i.test(line)
26508
+ ),
26509
+ fmt: () => "bg-clip-text + gradient"
26510
+ },
26511
+ // ── Gray text on a colored background (Tailwind). Shade ≥ 300 only: a pale
26512
+ // tint panel (bg-blue-50 info box) is a normal light ground, not "on color". ──
26513
+ {
26514
+ id: "gray-on-color",
26515
+ regex: /\btext-(?:gray|slate|zinc|neutral|stone)-(\d+)\b/g,
26516
+ test: (_m, line) => {
26517
+ const bg = line.match(
26518
+ /\bbg-(?:red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(\d+)\b/
26519
+ );
26520
+ return bg !== null && Number(bg[1] ?? 0) >= 300;
26521
+ },
26522
+ fmt: (m, line) => {
26523
+ const bg = line.match(
26524
+ /\bbg-(?:red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-\d+\b/
26525
+ );
26526
+ return `${cap2(m, 0)} on ${bg?.[0] || "?"}`;
26527
+ }
26528
+ },
26529
+ // ── AI color palette (Tailwind purple/indigo + banned hero hexes) ─────────
26530
+ {
26531
+ id: "ai-color-palette",
26532
+ regex: /\btext-(?:purple|violet|indigo)-(\d+)\b/g,
26533
+ test: (_m, line) => /\btext-(?:[2-9]xl)\b|<h[1-3]/i.test(line),
26534
+ fmt: (m) => `${cap2(m, 0)} on heading`
26535
+ },
26536
+ {
26537
+ id: "ai-color-palette",
26538
+ regex: /\bfrom-(?:purple|violet|indigo)-(\d+)\b/g,
26539
+ test: (_m, line) => /\bto-(?:purple|violet|indigo|blue|cyan|pink|fuchsia)-\d+\b/.test(line),
26540
+ fmt: (m) => `${cap2(m, 0)} gradient`
26541
+ },
26542
+ {
26543
+ id: "ai-color-palette",
26544
+ regex: /#[0-9a-f]{6}\b/gi,
26545
+ test: (m) => AI_PURPLE_HEXES.has(cap2(m, 0).toLowerCase()),
26546
+ fmt: (m) => `${cap2(m, 0)} (AI-cluster purple)`
26547
+ },
26548
+ // ── Cream/parchment ground (Tailwind + authored background hex) ───────────
26549
+ {
26550
+ id: "cream-palette",
26551
+ regex: /\bbg-(?:amber|orange|yellow|stone)-(?:50|100|200)\b/g,
26552
+ test: (m) => CREAM_TAILWIND_BG.has(cap2(m, 0)),
26553
+ fmt: (m) => `${cap2(m, 0)} (cream ground)`
26554
+ },
26555
+ {
26556
+ id: "cream-palette",
26557
+ regex: /background(?:-color)?\s*:\s*(#[0-9a-f]{6}\b|rgba?\([^)]+\))/gi,
26558
+ test: (m) => {
26559
+ const c = parseColor2(cap2(m, 1));
26560
+ return c !== null && isCreamColor(c);
26561
+ },
26562
+ fmt: (m) => `${cap2(m, 1)} (cream ground)`
26563
+ },
26564
+ // ── Bounce / elastic easing ───────────────────────────────────────────────
26565
+ {
26566
+ id: "bounce-easing",
26567
+ regex: /\banimate-bounce\b/g,
26568
+ test: () => true,
26569
+ fmt: () => "animate-bounce (Tailwind)"
26570
+ },
26571
+ {
26572
+ id: "bounce-easing",
26573
+ regex: /animation(?:-name)?\s*:\s*([^;{}]*(?:bounce|elastic|wobble|jiggle|spring)[^;{}]*)/gi,
26574
+ test: () => true,
26575
+ fmt: (m) => `animation: ${cap2(m, 1).trim()}`
26576
+ },
26577
+ {
26578
+ id: "bounce-easing",
26579
+ regex: /cubic-bezier\(\s*([\d.-]+)\s*,\s*([\d.-]+)\s*,\s*([\d.-]+)\s*,\s*([\d.-]+)\s*\)/g,
26580
+ test: (m) => {
26581
+ const y1 = Number.parseFloat(cap2(m, 2));
26582
+ const y2 = Number.parseFloat(cap2(m, 4));
26583
+ return y1 < -0.1 || y1 > 1.1 || y2 < -0.1 || y2 > 1.1;
26584
+ },
26585
+ fmt: (m) => `cubic-bezier(${cap2(m, 1)}, ${cap2(m, 2)}, ${cap2(m, 3)}, ${cap2(m, 4)})`
26586
+ },
26587
+ // ── Transitioning layout properties (jank) ────────────────────────────────
26588
+ {
26589
+ id: "layout-transition",
26590
+ // The lookahead skips Astro's view-transition directives (transition:name=,
26591
+ // transition:animate=, transition:persist) — markup idiom, not CSS.
26592
+ regex: /transition(?:-property)?\s*:\s*(?!name\s*=|animate\s*=|persist\b)([^;{}]+)/gi,
26593
+ test: (m) => {
26594
+ const val = cap2(m, 1).toLowerCase();
26595
+ if (/\ball\b/.test(val)) return false;
26596
+ return /\b(?:(?:max|min)-)?(?:width|height)\b|\bpadding\b|\bmargin\b/.test(val);
26597
+ },
26598
+ fmt: (m) => {
26599
+ const found = cap2(m, 1).match(
26600
+ /\b(?:(?:max|min)-)?(?:width|height)\b|\bpadding(?:-(?:top|right|bottom|left))?\b|\bmargin(?:-(?:top|right|bottom|left))?\b/gi
26601
+ );
26602
+ return `transition: ${found ? found.join(", ") : cap2(m, 1).trim()}`;
26603
+ }
26604
+ },
26605
+ // ── Broken image ──────────────────────────────────────────────────────────
26606
+ {
26607
+ id: "broken-image",
26608
+ regex: /<img\b[^>]*?\bsrc\s*=\s*(?:""|''|"\s+"|'\s+'|"#"|'#')/gi,
26609
+ test: () => true,
26610
+ fmt: (m) => cap2(m, 0).slice(0, 100)
26611
+ },
26612
+ {
26613
+ id: "broken-image",
26614
+ regex: /<img\b(?:(?!\bsrc\s*=)[^>])*>/gi,
26615
+ test: (m) => !/\bsrc\s*=/i.test(cap2(m, 0)),
26616
+ fmt: (m) => cap2(m, 0).slice(0, 100)
26617
+ }
26618
+ ];
26619
+ function stripHtmlToText(html) {
26620
+ 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, " ");
26621
+ }
26622
+ function blankAstroFrontmatter(text) {
26623
+ if (!text.startsWith("---")) return text;
26624
+ const lines = text.split("\n");
26625
+ if (!/^---\s*$/.test(lines[0] ?? "")) return text;
26626
+ for (let i = 1; i < lines.length; i++) {
26627
+ if (/^---\s*$/.test(lines[i] ?? "")) {
26628
+ for (let j = 0; j <= i; j++) lines[j] = "";
26629
+ return lines.join("\n");
26630
+ }
26631
+ }
26632
+ return text;
26633
+ }
26634
+ var ANALYZERS = [
26635
+ // Overused primary font (font-family declarations + Google Fonts URLs). Fires
26636
+ // at ≥1 and carries a line number → per file.
26637
+ {
26638
+ scope: "file",
26639
+ run: (text, file) => {
26640
+ const out = [];
26641
+ const seen = /* @__PURE__ */ new Set();
26642
+ for (const m of text.matchAll(/font-family\s*:\s*([^;}{]+)/gi)) {
26643
+ const first = firstFamily(cap2(m, 1));
26644
+ if (first && !GENERIC_FONTS.has(first) && isOverusedFont(first) && !seen.has(first)) {
26645
+ seen.add(first);
26646
+ out.push({ id: "overused-font", snippet: `font-family: ${first}`, file, line: lineOf(text, m.index ?? 0) });
26647
+ }
26648
+ }
26649
+ for (const m of text.matchAll(/fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi)) {
26650
+ for (const fam of googleFontFamilies(cap2(m, 0))) {
26651
+ if (isOverusedFont(fam) && !seen.has(fam)) {
26652
+ seen.add(fam);
26653
+ out.push({ id: "overused-font", snippet: `Google Fonts: ${fam}`, file, line: lineOf(text, m.index ?? 0) });
26654
+ }
26655
+ }
26656
+ }
26657
+ return out;
26658
+ }
26659
+ },
26660
+ // Single font across a substantial page — needs the whole page's font set.
26661
+ // Counts only the FIRST family of each stack (like overused-font): fallbacks
26662
+ // aren't choices, so `'Inter', 'Helvetica Neue'` everywhere is still one font.
26663
+ {
26664
+ scope: "page",
26665
+ run: (text, file) => {
26666
+ const fonts = /* @__PURE__ */ new Set();
26667
+ for (const m of text.matchAll(/font-family\s*:\s*([^;}{]+)/gi)) {
26668
+ const first = firstFamily(cap2(m, 1));
26669
+ if (first && !GENERIC_FONTS.has(first)) fonts.add(first);
26670
+ }
26671
+ for (const f of allGoogleFontFamilies(text)) fonts.add(f);
26672
+ if (fonts.size !== 1 || text.split("\n").length < 20) return [];
26673
+ const only = [...fonts][0] ?? "";
26674
+ return [{ id: "single-font", snippet: `only font used across the page is ${only}`, file }];
26675
+ }
26676
+ },
26677
+ // Flat type hierarchy (all sizes span <2×) — needs every size on the page.
26678
+ {
26679
+ scope: "page",
26680
+ run: (text, file) => {
26681
+ const sizes = /* @__PURE__ */ new Set();
26682
+ const REM = 16;
26683
+ for (const m of text.matchAll(/font-size\s*:\s*([\d.]+)(px|rem|em)\b/gi)) {
26684
+ const px = cap2(m, 2) === "px" ? num(m, 1) : num(m, 1) * REM;
26685
+ if (px > 0 && px < 200) sizes.add(Math.round(px * 10) / 10);
26686
+ }
26687
+ for (const [cls, px] of Object.entries(TAILWIND_TEXT_PX)) {
26688
+ if (new RegExp(`\\b${cls}\\b`).test(text)) sizes.add(px);
26689
+ }
26690
+ if (sizes.size < 3) return [];
26691
+ const sorted = [...sizes].sort((a, b) => a - b);
26692
+ const min = sorted[0] ?? 1;
26693
+ const max = sorted[sorted.length - 1] ?? 1;
26694
+ const ratio = max / min;
26695
+ if (ratio >= 2) return [];
26696
+ return [
26697
+ {
26698
+ id: "flat-type-hierarchy",
26699
+ snippet: `sizes ${sorted.map((s) => `${s}px`).join(", ")} (ratio ${ratio.toFixed(1)}:1)`,
26700
+ file
26701
+ }
26702
+ ];
26703
+ }
26704
+ },
26705
+ // Monotonous spacing (one value dominates) — needs every spacing value.
26706
+ {
26707
+ scope: "page",
26708
+ run: (text, file) => {
26709
+ const rounded = collectSpacingPx(text).map((v) => Math.round(v / 4) * 4);
26710
+ if (rounded.length < 10) return [];
26711
+ const counts = {};
26712
+ for (const v of rounded) counts[v] = (counts[v] || 0) + 1;
26713
+ const maxCount = Math.max(...Object.values(counts));
26714
+ const pct = maxCount / rounded.length;
26715
+ const unique = [...new Set(rounded)].filter((v) => v > 0);
26716
+ if (pct <= 0.6 || unique.length > 3) return [];
26717
+ const dominant = Object.entries(counts).sort((a, b) => b[1] - a[1])[0]?.[0] ?? "?";
26718
+ return [
26719
+ {
26720
+ id: "monotonous-spacing",
26721
+ snippet: `~${dominant}px used ${maxCount}/${rounded.length} times (${Math.round(pct * 100)}%)`,
26722
+ file
26723
+ }
26724
+ ];
26725
+ }
26726
+ },
26727
+ // Em-dash overuse (density-gated) — density is measured over the page's copy.
26728
+ {
26729
+ scope: "page",
26730
+ run: (text, file) => {
26731
+ const body = stripHtmlToText(text).replace(/&mdash;|&#0*8212;|&#x0*2014;/gi, "\u2014");
26732
+ const count = (body.match(/[—]|(?<!-)--(?!-)(?=\S)/g) ?? []).length;
26733
+ if (count < EM_DASH_FLOOR) return [];
26734
+ if (body.length > count * EM_DASH_CHARS_PER_DASH) return [];
26735
+ return [{ id: "em-dash-overuse", snippet: `${count} em-dashes in body copy`, file }];
26736
+ }
26737
+ },
26738
+ // Marketing buzzwords. Fires at ≥1 → per file (keeps the offending file named).
26739
+ {
26740
+ scope: "file",
26741
+ run: (text, file) => {
26742
+ const body = stripHtmlToText(text);
26743
+ const lower = body.toLowerCase();
26744
+ let count = 0;
26745
+ let firstSample = "";
26746
+ for (const phrase of BUZZWORDS) {
26747
+ let from = 0;
26748
+ for (; ; ) {
26749
+ const idx = lower.indexOf(phrase, from);
26750
+ if (idx === -1) break;
26751
+ count++;
26752
+ if (!firstSample) {
26753
+ firstSample = body.slice(Math.max(0, idx - 12), Math.min(body.length, idx + phrase.length + 12)).trim();
26754
+ }
26755
+ from = idx + phrase.length;
26756
+ }
26757
+ }
26758
+ if (count === 0) return [];
26759
+ return [
26760
+ {
26761
+ id: "marketing-buzzword",
26762
+ snippet: `${count} buzzword phrase${count === 1 ? "" : "s"}: "${firstSample}"`,
26763
+ file
26764
+ }
26765
+ ];
26766
+ }
26767
+ },
26768
+ // Aphoristic cadence (manufactured contrast + short rebuttal) — the ≥3 gate
26769
+ // needs the whole page's copy, else it's diluted across components.
26770
+ {
26771
+ scope: "page",
26772
+ run: (text, file) => {
26773
+ const body = stripHtmlToText(text);
26774
+ let count = 0;
26775
+ let firstSample = "";
26776
+ for (const m of body.matchAll(/\bNot an? [a-z][^.!?]{1,40}[.!]\s+[A-Z][^.!?]{1,60}[.!]/g)) {
26777
+ count++;
26778
+ if (!firstSample) firstSample = cap2(m, 0).trim().slice(0, 80);
26779
+ }
26780
+ for (const m of body.matchAll(/\b[A-Z][^.!?]{4,80}[.!]\s+(No|Just)\s+[a-z][^.!?]{2,60}[.!]/g)) {
26781
+ count++;
26782
+ if (!firstSample) firstSample = cap2(m, 0).trim().slice(0, 80);
26783
+ }
26784
+ if (count < 3) return [];
26785
+ return [{ id: "aphoristic-cadence", snippet: `${count} aphoristic constructions: "${firstSample}"`, file }];
26786
+ }
26787
+ },
26788
+ // Gradient text in authored CSS. A rule block spans lines, so this scans a
26789
+ // ±6-line window around each `background-clip: text` instead of one line
26790
+ // (the Tailwind class form stays a line matcher above).
26791
+ {
26792
+ scope: "file",
26793
+ run: (text, file) => {
26794
+ const out = [];
26795
+ const lines = text.split("\n");
26796
+ for (const m of text.matchAll(/(?:-webkit-)?background-clip\s*:\s*text/gi)) {
26797
+ const line = lineOf(text, m.index ?? 0);
26798
+ const window = lines.slice(Math.max(0, line - 7), Math.min(lines.length, line + 6)).join("\n");
26799
+ if (/gradient\(/i.test(window)) {
26800
+ out.push({ id: "gradient-text", snippet: "background-clip: text + gradient", file, line });
26801
+ }
26802
+ }
26803
+ return out;
26804
+ }
26805
+ },
26806
+ // Dark glow: a zero-offset chromatic shadow used as decoration. Line-attributed
26807
+ // → per file.
26808
+ {
26809
+ scope: "file",
26810
+ run: (text, file) => {
26811
+ const out = [];
26812
+ for (const m of text.matchAll(/(?:box|text)-shadow\s*:\s*([^;{}]+)/gi)) {
26813
+ if (shadowIsGlow(cap2(m, 1))) {
26814
+ out.push({
26815
+ id: "dark-glow",
26816
+ snippet: `shadow: ${cap2(m, 1).trim().slice(0, 60)}`,
26817
+ file,
26818
+ line: lineOf(text, m.index ?? 0)
26819
+ });
26820
+ }
26821
+ }
26822
+ return out;
26823
+ }
26824
+ },
26825
+ // Marquee (<marquee> element). Fires at ≥1 → per file.
26826
+ {
26827
+ scope: "file",
26828
+ run: (text, file) => /<marquee\b/i.test(text) ? [{ id: "marquee", snippet: "<marquee> element", file }] : []
26829
+ }
26830
+ ];
26831
+ function collectSpacingPx(text) {
26832
+ const vals = [];
26833
+ const push = (v) => {
26834
+ if (v > 0 && v < 200) vals.push(v);
26835
+ };
26836
+ for (const m of text.matchAll(/(?:padding|margin)(?:-(?:top|right|bottom|left))?\s*:\s*(\d+)px/gi)) push(num(m, 1));
26837
+ for (const m of text.matchAll(/(?:padding|margin)(?:-(?:top|right|bottom|left))?\s*:\s*([\d.]+)rem/gi))
26838
+ push(Math.round(Number.parseFloat(cap2(m, 1)) * 16));
26839
+ for (const m of text.matchAll(/\b(?:p|px|py|pt|pb|pl|pr|m|mx|my|mt|mb|ml|mr|gap(?:-[xy])?|space-[xy])-([\d.]+)\b/g))
26840
+ push(Number.parseFloat(cap2(m, 1)) * 4);
26841
+ return vals;
26842
+ }
26843
+ function shadowIsGlow(value) {
26844
+ const first = (value.split(/,(?![^(]*\))/)[0] ?? "").trim();
26845
+ const colorMatch = first.match(/#[0-9a-f]{3,8}\b|rgba?\([^)]+\)|hsla?\([^)]+\)/i);
26846
+ if (!colorMatch) return false;
26847
+ const lengthsPart = first.replace(colorMatch[0], " ").replace(/\binset\b/gi, " ");
26848
+ const offsets = lengthsPart.match(/-?\d*\.?\d+(?:px|rem|em)?/g);
26849
+ if (!offsets || offsets.length < 3) return false;
26850
+ const x = Number.parseFloat(offsets[0] ?? "0");
26851
+ const y = Number.parseFloat(offsets[1] ?? "0");
26852
+ const blur = Number.parseFloat(offsets[2] ?? "0");
26853
+ if (!(x === 0 && y === 0 && blur > 4)) return false;
26854
+ const c = parseColor2(colorMatch[0]);
26855
+ return c !== null && hasChroma(c, 30);
26856
+ }
26857
+ function firstFamily(decl) {
26858
+ return (decl.split(",")[0] ?? "").trim().replace(/^['"]|['"]$/g, "").toLowerCase();
26859
+ }
26860
+ function googleFontFamilies(url) {
26861
+ const q = url.indexOf("?");
26862
+ if (q === -1) return [];
26863
+ const params = new URLSearchParams(url.slice(q + 1).replace(/&amp;/g, "&"));
26864
+ const out = [];
26865
+ for (const value of params.getAll("family")) {
26866
+ for (const part of value.split("|")) out.push((part.split(":")[0] ?? "").trim().toLowerCase());
26867
+ }
26868
+ return out.filter(Boolean);
26869
+ }
26870
+ function allGoogleFontFamilies(text) {
26871
+ const out = [];
26872
+ for (const m of text.matchAll(/fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi))
26873
+ out.push(...googleFontFamilies(cap2(m, 0)));
26874
+ return out;
26875
+ }
26876
+ function lineOf(text, index) {
26877
+ let line = 1;
26878
+ for (let i = 0; i < index && i < text.length; i++) if (text[i] === "\n") line++;
26879
+ return line;
26880
+ }
26881
+ function detectSource(source) {
26882
+ const file = source.path;
26883
+ const text = blankAstroFrontmatter(source.text);
26884
+ const findings = [];
26885
+ const lines = text.split("\n");
26886
+ for (const matcher of LINE_MATCHERS) {
26887
+ for (let i = 0; i < lines.length; i++) {
26888
+ const line = lines[i] ?? "";
26889
+ for (const m of line.matchAll(matcher.regex)) {
26890
+ if (matcher.test(m, line)) findings.push({ id: matcher.id, snippet: matcher.fmt(m, line), file, line: i + 1 });
26891
+ }
26892
+ }
26893
+ }
26894
+ for (const analyzer of ANALYZERS) {
26895
+ if (analyzer.scope === "file") findings.push(...analyzer.run(text, file));
26896
+ }
26897
+ return dedupe(findings);
26898
+ }
26899
+ function detectPage(sources) {
26900
+ if (sources.length === 0) return [];
26901
+ const combined = sources.map((s) => blankAstroFrontmatter(s.text)).join("\n");
26902
+ const pageFile = sources.find((s) => s.path === "index.astro" || s.path.endsWith("/index.astro"))?.path ?? sources[0]?.path ?? "index.astro";
26903
+ const findings = [];
26904
+ for (const analyzer of ANALYZERS) {
26905
+ if (analyzer.scope === "page") findings.push(...analyzer.run(combined, pageFile));
26906
+ }
26907
+ return dedupe(findings);
26908
+ }
26909
+ function dedupe(findings) {
26910
+ const out = [];
26911
+ for (const f of findings) {
26912
+ const dupe = out.some(
26913
+ (d) => d.id === f.id && d.snippet === f.snippet && Math.abs((d.line ?? 0) - (f.line ?? 0)) <= 2
26914
+ );
26915
+ if (!dupe) out.push(f);
26916
+ }
26917
+ return out;
26918
+ }
26919
+
26920
+ // src/engine/landing/lib/critique.ts
26921
+ var FAMILIES = ["typography", "color", "borders_depth", "motion", "spacing", "copy", "integrity"];
26922
+ function round4(n) {
26923
+ return Math.round(n * 100) / 100;
26924
+ }
26925
+ function clamp012(n) {
26926
+ return Math.max(0, Math.min(1, n));
26927
+ }
26928
+ function applyBrand(id, snippet, severity, brand) {
26929
+ const meta = RULE_META[id];
26930
+ if (!meta?.brandAware || !brand.hasTokens) return { severity, brandDrift: false };
26931
+ 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);
26932
+ return matched ? { severity: "advisory", brandDrift: true } : { severity, brandDrift: false };
26933
+ }
26934
+ function critiqueLanding(input) {
26935
+ const sources = Array.isArray(input.sources) ? input.sources : [];
26936
+ const findings = [];
26937
+ const raws = [];
26938
+ for (const source of sources) raws.push(...detectSource(source));
26939
+ raws.push(...detectPage(sources));
26940
+ for (const raw of raws) {
26941
+ const meta = RULE_META[raw.id];
26942
+ if (!meta) continue;
26943
+ const { severity, brandDrift } = applyBrand(raw.id, raw.snippet, meta.severity, input.brand);
26944
+ findings.push({
26945
+ id: raw.id,
26946
+ severity,
26947
+ family: meta.family,
26948
+ snippet: raw.snippet,
26949
+ file: raw.file,
26950
+ line: raw.line,
26951
+ note: meta.note,
26952
+ ...brandDrift ? { brandDrift: true } : {}
26953
+ });
26954
+ }
26955
+ const dimensions = FAMILIES.map((family) => {
26956
+ const fam = findings.filter((f) => f.family === family);
26957
+ const penalty = fam.reduce((s, f) => s + SEVERITY_WEIGHT[f.severity], 0);
26958
+ const score = round4(clamp012(1 - penalty));
26959
+ const note = fam.length === 0 ? "clean" : `${fam.length} finding(s): ${describeCounts(fam)}`;
26960
+ return { dimension: family, score, note };
26961
+ });
26962
+ const overall = round4(dimensions.reduce((s, d) => s + d.score, 0) / dimensions.length);
26963
+ const counts = {
26964
+ block: findings.filter((f) => f.severity === "block").length,
26965
+ warn: findings.filter((f) => f.severity === "warn").length,
26966
+ advisory: findings.filter((f) => f.severity === "advisory").length
26967
+ };
26968
+ return { advisory: true, overall, dimensions, findings, counts };
26969
+ }
26970
+ function describeCounts(findings) {
26971
+ const b = findings.filter((f) => f.severity === "block").length;
26972
+ const w = findings.filter((f) => f.severity === "warn").length;
26973
+ const a = findings.filter((f) => f.severity === "advisory").length;
26974
+ return [b ? `${b} block` : "", w ? `${w} warn` : "", a ? `${a} advisory` : ""].filter(Boolean).join(", ");
26975
+ }
26976
+
26977
+ // src/commands/landing/snapshot.ts
26978
+ import { mkdir as mkdir7, rename as rename2, writeFile as writeFile10 } from "fs/promises";
26979
+ import path24 from "path";
26980
+ var CRITIC_VERSION = "1";
26981
+ function critiqueCacheDir(projectRoot) {
26982
+ return path24.join(projectRoot, ".cache", "landing-critique");
26983
+ }
26984
+ function snapshotPath(projectRoot, slug) {
26985
+ return path24.join(critiqueCacheDir(projectRoot), `${slug}.json`);
26986
+ }
26987
+ async function writeCritiqueSnapshot(projectRoot, snapshot) {
26988
+ await mkdir7(critiqueCacheDir(projectRoot), { recursive: true });
26989
+ const dest = snapshotPath(projectRoot, snapshot.slug);
26990
+ const tmp = `${dest}.tmp`;
26991
+ await writeFile10(tmp, `${JSON.stringify(snapshot, null, 2)}
26992
+ `, "utf8");
26993
+ await rename2(tmp, dest);
26994
+ }
26995
+
26996
+ // src/commands/landing/source-version.ts
26997
+ import { readdir as readdir7, readFile as readFile21, stat as stat5 } from "fs/promises";
26998
+ import path25 from "path";
26999
+ async function landingSourceRelPaths(landingDir) {
27000
+ const rel = [];
27001
+ if (await isFile(path25.join(landingDir, "index.astro"))) rel.push("index.astro");
27002
+ const componentsDir = path25.join(landingDir, "_components");
27003
+ for (const abs of await walkAstro(componentsDir)) {
27004
+ rel.push(path25.relative(landingDir, abs).split(path25.sep).join("/"));
27005
+ }
27006
+ return rel.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
27007
+ }
27008
+ async function readLandingSources(landingDir) {
27009
+ const rel = await landingSourceRelPaths(landingDir);
27010
+ const out = [];
27011
+ for (const r of rel) out.push({ path: r, text: await readFile21(path25.join(landingDir, r), "utf8") });
27012
+ return out;
27013
+ }
27014
+ async function computeLandingSourceSha(landingDir) {
27015
+ const rel = await landingSourceRelPaths(landingDir);
27016
+ const parts = [];
27017
+ for (const r of rel) {
27018
+ let bytes;
27019
+ try {
27020
+ bytes = await readFile21(path25.join(landingDir, r));
27021
+ } catch {
27022
+ bytes = Buffer.alloc(0);
27023
+ }
27024
+ parts.push(Buffer.from(`${r}\0${bytes.length}\0`), bytes);
27025
+ }
27026
+ return sha256Hex(Buffer.concat(parts));
27027
+ }
27028
+ async function isFile(p) {
27029
+ try {
27030
+ return (await stat5(p)).isFile();
27031
+ } catch {
27032
+ return false;
27033
+ }
27034
+ }
27035
+ async function walkAstro(dir) {
27036
+ let entries;
27037
+ try {
27038
+ entries = await readdir7(dir, { withFileTypes: true });
27039
+ } catch {
27040
+ return [];
27041
+ }
27042
+ const out = [];
27043
+ for (const entry of entries) {
27044
+ const abs = path25.join(dir, entry.name);
27045
+ if (entry.isDirectory()) out.push(...await walkAstro(abs));
27046
+ else if (entry.isFile() && entry.name.endsWith(".astro")) out.push(abs);
27047
+ }
27048
+ return out;
27049
+ }
27050
+
27051
+ // src/commands/landing/critique.ts
27052
+ registerSchema({
27053
+ command: "landing.critique",
27054
+ description: "Start here: `baker landing critique my-landing` after building or editing a landing. Deterministic design-quality critic (ADVISORY \u2014 findings never fail it). 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.",
27055
+ args: {
27056
+ slug: { type: "string", description: "Landing slug (folder under src/pages/)", required: true },
27057
+ full: {
27058
+ type: "boolean",
27059
+ description: "Also print advisory-tier findings (default: block + warn only)",
27060
+ required: false
27061
+ }
27062
+ }
27063
+ });
27064
+ var SLUG_RE = /^[a-z0-9][a-z0-9._-]*$/i;
27065
+ function fail4(code, message, fix) {
27066
+ process.stderr.write(
27067
+ `${JSON.stringify({ ok: false, error: { code, message, ...fix ? { fix } : {} } }, null, 2)}
27068
+ `
27069
+ );
27070
+ process.exit(2);
27071
+ }
27072
+ var critiqueCommand2 = defineCommand133({
27073
+ meta: {
27074
+ name: "critique",
27075
+ description: "Start here: `baker landing critique <slug>` after building or editing a landing. Deterministic design-quality critic (ADVISORY \u2014 findings never fail it). 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 publishing requires \u2014 run it before finishing a landing."
27076
+ },
27077
+ args: {
27078
+ slug: { type: "positional", required: true, description: "Landing slug (folder under src/pages/)" },
27079
+ full: { type: "boolean", description: "Also print advisory-tier findings", default: false }
27080
+ },
27081
+ async run({ args }) {
27082
+ const slug = String(args.slug);
27083
+ const projectRoot = process.cwd();
27084
+ if (!SLUG_RE.test(slug) || slug.includes("..")) {
27085
+ fail4(
27086
+ "INVALID_SLUG",
27087
+ `"${slug}" is not a landing slug \u2014 use the folder name directly under src/pages/ (letters, digits, dashes; not a path, not a _-private folder).`,
27088
+ { availableSlugs: await listLandingSlugs(projectRoot) }
27089
+ );
27090
+ }
27091
+ const landingDir = path26.resolve(projectRoot, "src", "pages", slug);
27092
+ if (!await isDir(landingDir)) {
27093
+ fail4("NOT_FOUND", `No landing at src/pages/${slug}/`, {
27094
+ availableSlugs: await listLandingSlugs(projectRoot)
27095
+ });
27096
+ }
27097
+ const [sources, brand, sourceSha] = await Promise.all([
27098
+ readLandingSources(landingDir),
27099
+ loadBrandTokens(projectRoot),
27100
+ computeLandingSourceSha(landingDir)
27101
+ ]);
27102
+ const report = critiqueLanding({ slug, sources, brand });
27103
+ let snapshotFailed = false;
27104
+ try {
27105
+ await writeCritiqueSnapshot(projectRoot, {
27106
+ slug,
27107
+ sourceSha,
27108
+ criticVersion: CRITIC_VERSION,
27109
+ at: (/* @__PURE__ */ new Date()).toISOString(),
27110
+ blockCount: report.counts.block,
27111
+ warnCount: report.counts.warn
27112
+ });
27113
+ } catch {
27114
+ snapshotFailed = true;
27115
+ }
27116
+ const shown = report.findings.filter((f) => args.full ? true : f.severity !== "advisory");
27117
+ const hints = [
27118
+ 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.",
27119
+ ...snapshotFailed ? [
27120
+ "Could not record the critique snapshot (.cache/ not writable?) \u2014 the publish quality gate will still report this landing as un-critiqued."
27121
+ ] : []
27122
+ ];
27123
+ process.stdout.write(
27124
+ `${JSON.stringify(
27125
+ {
27126
+ ok: true,
27127
+ data: {
27128
+ advisory: true,
27129
+ slug,
27130
+ overall: report.overall,
27131
+ counts: report.counts,
27132
+ dimensions: report.dimensions,
27133
+ findings: shown.map(present)
27134
+ },
27135
+ hints
27136
+ },
27137
+ null,
27138
+ 2
27139
+ )}
27140
+ `
27141
+ );
27142
+ }
27143
+ });
27144
+ async function listLandingSlugs(projectRoot) {
27145
+ try {
27146
+ const entries = await readdir8(path26.join(projectRoot, "src", "pages"), { withFileTypes: true });
27147
+ return entries.filter((e) => e.isDirectory() && !e.name.startsWith("_") && !e.name.startsWith(".")).map((e) => e.name).sort();
27148
+ } catch {
27149
+ return [];
27150
+ }
27151
+ }
27152
+ function present(f) {
27153
+ return {
27154
+ id: f.id,
27155
+ severity: f.severity,
27156
+ family: f.family,
27157
+ where: f.line ? `${f.file}:${f.line}` : f.file,
27158
+ snippet: f.snippet,
27159
+ note: f.note,
27160
+ ...f.brandDrift ? { brandDrift: true } : {}
27161
+ };
27162
+ }
27163
+ async function isDir(p) {
27164
+ try {
27165
+ return (await stat6(p)).isDirectory();
27166
+ } catch {
27167
+ return false;
27168
+ }
27169
+ }
27170
+
27171
+ // src/commands/landing/index.ts
27172
+ var landingCommand = defineCommand134({
27173
+ meta: {
27174
+ name: "landing",
27175
+ description: `Design-quality tools for landing pages (src/pages/<slug>/).
27176
+
27177
+ Start here: \`baker landing critique <slug>\` after building or editing a landing.
27178
+
27179
+ Subcommands:
27180
+ 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.`
27181
+ },
27182
+ subCommands: {
27183
+ critique: critiqueCommand2
27184
+ }
27185
+ });
27186
+
27187
+ // src/commands/mcp/index.ts
27188
+ import { defineCommand as defineCommand135 } from "citty";
26015
27189
  var SCOPES = ["user", "user_org", "company", "org"];
26016
27190
  function parseScope(raw) {
26017
27191
  const scope = raw === void 0 ? "company" : String(raw);
@@ -26037,7 +27211,7 @@ function parseHeaders(raw) {
26037
27211
  }
26038
27212
  return Object.keys(headers).length > 0 ? headers : void 0;
26039
27213
  }
26040
- function fail4(err) {
27214
+ function fail5(err) {
26041
27215
  if (err instanceof ApiError) {
26042
27216
  writeJson({ ok: false, error: { code: err.code, message: err.message } });
26043
27217
  process.exit(1);
@@ -26050,7 +27224,7 @@ registerSchema({
26050
27224
  description: "List every third-party tool this chat can reach: managed integrations (Attio, Slack, Gmail, Google Sheets, \u2026) plus custom MCP servers. Start here when the user mentions an external tool.",
26051
27225
  args: {}
26052
27226
  });
26053
- var connectedCommand = defineCommand133({
27227
+ var connectedCommand = defineCommand135({
26054
27228
  meta: {
26055
27229
  name: "connected",
26056
27230
  description: `List all connected third-party tools \u2014 managed integrations plus custom MCP servers.
@@ -26088,7 +27262,7 @@ A tool the user names that is NOT listed here is simply not connected yet.`
26088
27262
  hints
26089
27263
  });
26090
27264
  } catch (err) {
26091
- fail4(err);
27265
+ fail5(err);
26092
27266
  }
26093
27267
  }
26094
27268
  });
@@ -26097,7 +27271,7 @@ registerSchema({
26097
27271
  description: "List the custom MCP servers this company's chats see (org + company + your own user scope).",
26098
27272
  args: {}
26099
27273
  });
26100
- var listCommand8 = defineCommand133({
27274
+ var listCommand8 = defineCommand135({
26101
27275
  meta: { name: "list", description: "List custom MCP servers visible to this company's chats." },
26102
27276
  run: async () => {
26103
27277
  try {
@@ -26108,7 +27282,7 @@ var listCommand8 = defineCommand133({
26108
27282
  );
26109
27283
  writeJson({ ok: true, data: data.servers });
26110
27284
  } catch (err) {
26111
- fail4(err);
27285
+ fail5(err);
26112
27286
  }
26113
27287
  }
26114
27288
  });
@@ -26122,7 +27296,7 @@ registerSchema({
26122
27296
  header: { type: "string", description: 'Auth header "Key: Value" (repeatable)', required: false }
26123
27297
  }
26124
27298
  });
26125
- var addCommand = defineCommand133({
27299
+ var addCommand = defineCommand135({
26126
27300
  meta: {
26127
27301
  name: "add",
26128
27302
  description: `Register a custom MCP server. Tools appear as mcp__<name>__* on the NEXT message.
@@ -26154,7 +27328,7 @@ Examples:
26154
27328
  });
26155
27329
  writeJson({ ok: true, data });
26156
27330
  } catch (err) {
26157
- fail4(err);
27331
+ fail5(err);
26158
27332
  }
26159
27333
  }
26160
27334
  });
@@ -26163,7 +27337,7 @@ registerSchema({
26163
27337
  description: "Remove a company custom MCP server by name.",
26164
27338
  args: { name: { type: "string", description: "Server name to remove", required: true } }
26165
27339
  });
26166
- var removeCommand4 = defineCommand133({
27340
+ var removeCommand4 = defineCommand135({
26167
27341
  meta: {
26168
27342
  name: "remove",
26169
27343
  description: `Remove a company custom MCP server by name.
@@ -26181,11 +27355,11 @@ Example:
26181
27355
  });
26182
27356
  writeJson({ ok: true, data });
26183
27357
  } catch (err) {
26184
- fail4(err);
27358
+ fail5(err);
26185
27359
  }
26186
27360
  }
26187
27361
  });
26188
- var mcpCommand = defineCommand133({
27362
+ var mcpCommand = defineCommand135({
26189
27363
  meta: {
26190
27364
  name: "mcp",
26191
27365
  description: `Third-party tools for this company \u2014 see what's connected, register custom HTTPS MCP endpoints.
@@ -26211,10 +27385,10 @@ Full guide: __tooling__/docs/tools/baker/mcp.md`
26211
27385
  });
26212
27386
 
26213
27387
  // src/commands/research/index.ts
26214
- import { defineCommand as defineCommand144 } from "citty";
27388
+ import { defineCommand as defineCommand146 } from "citty";
26215
27389
 
26216
27390
  // src/commands/research/advertisers.ts
26217
- import { defineCommand as defineCommand134 } from "citty";
27391
+ import { defineCommand as defineCommand136 } from "citty";
26218
27392
 
26219
27393
  // src/commands/research/output.ts
26220
27394
  var RESEARCH_DATA_NOTE = "Estimates based on third-party SERP data \u2014 not exact figures. Use for directional insights, not precise measurement.";
@@ -26327,7 +27501,7 @@ var FIELDS3 = {
26327
27501
  etv: "Estimated traffic value (USD)",
26328
27502
  visibility: "SERP visibility score (0-1)"
26329
27503
  };
26330
- var advertisersCommand = defineCommand134({
27504
+ var advertisersCommand = defineCommand136({
26331
27505
  meta: {
26332
27506
  name: "advertisers",
26333
27507
  description: `Find domains competing for a keyword in Google SERPs.
@@ -26374,7 +27548,7 @@ Examples:
26374
27548
  });
26375
27549
 
26376
27550
  // src/commands/research/autocomplete.ts
26377
- import { defineCommand as defineCommand135 } from "citty";
27551
+ import { defineCommand as defineCommand137 } from "citty";
26378
27552
  registerSchema({
26379
27553
  command: "research.autocomplete",
26380
27554
  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).",
@@ -26397,7 +27571,7 @@ registerSchema({
26397
27571
  var FIELDS4 = {
26398
27572
  suggestion: "Autocomplete suggestion from Google"
26399
27573
  };
26400
- var autocompleteCommand = defineCommand135({
27574
+ var autocompleteCommand = defineCommand137({
26401
27575
  meta: {
26402
27576
  name: "autocomplete",
26403
27577
  description: `Get Google Autocomplete suggestions for keyword expansion.
@@ -26443,7 +27617,7 @@ Examples:
26443
27617
  });
26444
27618
 
26445
27619
  // src/commands/research/countries.ts
26446
- import { defineCommand as defineCommand136 } from "citty";
27620
+ import { defineCommand as defineCommand138 } from "citty";
26447
27621
  registerSchema({
26448
27622
  command: "research.countries",
26449
27623
  description: "List all supported country codes for --location flag in research commands.",
@@ -26500,7 +27674,7 @@ var FIELDS5 = {
26500
27674
  code: "Country code to pass as --location",
26501
27675
  name: "Country name"
26502
27676
  };
26503
- var countriesCommand = defineCommand136({
27677
+ var countriesCommand = defineCommand138({
26504
27678
  meta: {
26505
27679
  name: "countries",
26506
27680
  description: "List all supported country codes for --location flag."
@@ -26511,7 +27685,7 @@ var countriesCommand = defineCommand136({
26511
27685
  });
26512
27686
 
26513
27687
  // src/commands/research/intent.ts
26514
- import { defineCommand as defineCommand137 } from "citty";
27688
+ import { defineCommand as defineCommand139 } from "citty";
26515
27689
  registerSchema({
26516
27690
  command: "research.intent",
26517
27691
  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.",
@@ -26534,7 +27708,7 @@ var FIELDS6 = {
26534
27708
  intent: "Primary Google Search intent: informational, navigational, commercial, transactional",
26535
27709
  probability: "Confidence score 0.0-1.0"
26536
27710
  };
26537
- var intentCommand = defineCommand137({
27711
+ var intentCommand = defineCommand139({
26538
27712
  meta: {
26539
27713
  name: "intent",
26540
27714
  description: `Classify Google Search intent for keywords. Returns intent type and confidence.
@@ -26582,7 +27756,7 @@ Examples:
26582
27756
  });
26583
27757
 
26584
27758
  // src/commands/research/keyword-gap.ts
26585
- import { defineCommand as defineCommand138 } from "citty";
27759
+ import { defineCommand as defineCommand140 } from "citty";
26586
27760
  registerSchema({
26587
27761
  command: "research.keyword-gap",
26588
27762
  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.",
@@ -26611,7 +27785,7 @@ var FIELDS7 = {
26611
27785
  cpc: "Cost per click USD",
26612
27786
  their_position: "Competitor's ranking position"
26613
27787
  };
26614
- var keywordGapCommand = defineCommand138({
27788
+ var keywordGapCommand = defineCommand140({
26615
27789
  meta: {
26616
27790
  name: "keyword-gap",
26617
27791
  description: `Find keywords a competitor has that you don't. Supports pagination via --offset.
@@ -26685,7 +27859,7 @@ Examples:
26685
27859
  });
26686
27860
 
26687
27861
  // src/commands/research/keywords-for-site.ts
26688
- import { defineCommand as defineCommand139 } from "citty";
27862
+ import { defineCommand as defineCommand141 } from "citty";
26689
27863
  registerSchema({
26690
27864
  command: "research.keywords-for-site",
26691
27865
  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.",
@@ -26718,7 +27892,7 @@ var FIELDS8 = {
26718
27892
  competition: "LOW, MEDIUM, or HIGH",
26719
27893
  competition_index: "Competition score 0-100"
26720
27894
  };
26721
- var keywordsForSiteCommand = defineCommand139({
27895
+ var keywordsForSiteCommand = defineCommand141({
26722
27896
  meta: {
26723
27897
  name: "keywords-for-site",
26724
27898
  description: `Get keywords a competitor targets in Google. Use --type to filter paid/organic.
@@ -26771,7 +27945,7 @@ Examples:
26771
27945
  });
26772
27946
 
26773
27947
  // src/commands/research/languages.ts
26774
- import { defineCommand as defineCommand140 } from "citty";
27948
+ import { defineCommand as defineCommand142 } from "citty";
26775
27949
  registerSchema({
26776
27950
  command: "research.languages",
26777
27951
  description: "List all supported language codes for --language flag in research commands.",
@@ -26801,7 +27975,7 @@ var FIELDS9 = {
26801
27975
  code: "Language code to pass as --language",
26802
27976
  name: "Language name (also accepted by --language)"
26803
27977
  };
26804
- var languagesCommand2 = defineCommand140({
27978
+ var languagesCommand2 = defineCommand142({
26805
27979
  meta: {
26806
27980
  name: "languages",
26807
27981
  description: "List all supported language codes for --language flag."
@@ -26812,7 +27986,7 @@ var languagesCommand2 = defineCommand140({
26812
27986
  });
26813
27987
 
26814
27988
  // src/commands/research/lighthouse.ts
26815
- import { defineCommand as defineCommand141 } from "citty";
27989
+ import { defineCommand as defineCommand143 } from "citty";
26816
27990
  registerSchema({
26817
27991
  command: "research.lighthouse",
26818
27992
  description: "Landing page performance audit. Returns metrics that affect Google Ads Quality Score and CPC.",
@@ -26831,7 +28005,7 @@ var FIELDS10 = {
26831
28005
  speed_index_ms: "Speed Index in ms (good: < 3400)",
26832
28006
  interactive_ms: "Time to Interactive in ms (good: < 3800)"
26833
28007
  };
26834
- var lighthouseCommand = defineCommand141({
28008
+ var lighthouseCommand = defineCommand143({
26835
28009
  meta: {
26836
28010
  name: "lighthouse",
26837
28011
  description: `Landing page performance audit. Metrics affecting Google Ads Quality Score.
@@ -26869,7 +28043,7 @@ Examples:
26869
28043
  });
26870
28044
 
26871
28045
  // src/commands/research/relevant-pages.ts
26872
- import { defineCommand as defineCommand142 } from "citty";
28046
+ import { defineCommand as defineCommand144 } from "citty";
26873
28047
  registerSchema({
26874
28048
  command: "research.relevant-pages",
26875
28049
  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).",
@@ -26895,7 +28069,7 @@ var FIELDS11 = {
26895
28069
  keywords: "Total organic keywords the page ranks for",
26896
28070
  top_10: "Keywords in positions 1-10"
26897
28071
  };
26898
- var relevantPagesCommand = defineCommand142({
28072
+ var relevantPagesCommand = defineCommand144({
26899
28073
  meta: {
26900
28074
  name: "relevant-pages",
26901
28075
  description: `Get the top pages of a competitor domain with traffic data.
@@ -26941,7 +28115,7 @@ Examples:
26941
28115
  });
26942
28116
 
26943
28117
  // src/commands/research/web.ts
26944
- import { defineCommand as defineCommand143 } from "citty";
28118
+ import { defineCommand as defineCommand145 } from "citty";
26945
28119
  registerSchema({
26946
28120
  command: "research.web",
26947
28121
  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).",
@@ -26992,7 +28166,7 @@ async function runDeepResearch(question) {
26992
28166
  }
26993
28167
  throw new Error("Deep research timed out");
26994
28168
  }
26995
- var webCommand = defineCommand143({
28169
+ var webCommand = defineCommand145({
26996
28170
  meta: {
26997
28171
  name: "web",
26998
28172
  description: `Search the web with AI to answer any open-ended marketing question. Uses live internet data via Google Search.
@@ -27052,7 +28226,7 @@ Examples:
27052
28226
  });
27053
28227
 
27054
28228
  // src/commands/research/index.ts
27055
- var researchCommand = defineCommand144({
28229
+ var researchCommand = defineCommand146({
27056
28230
  meta: {
27057
28231
  name: "research",
27058
28232
  description: `Competitive intelligence and AI-powered research commands.
@@ -27093,10 +28267,10 @@ Full guide: __tooling__/docs/tools/baker/research.md`
27093
28267
  });
27094
28268
 
27095
28269
  // src/commands/scheduled-actions/index.ts
27096
- import { defineCommand as defineCommand151 } from "citty";
28270
+ import { defineCommand as defineCommand153 } from "citty";
27097
28271
 
27098
28272
  // src/commands/scheduled-actions/create.ts
27099
- import { defineCommand as defineCommand145 } from "citty";
28273
+ import { defineCommand as defineCommand147 } from "citty";
27100
28274
 
27101
28275
  // src/commands/scheduled-actions/shared.ts
27102
28276
  var TEMP_SCHEDULED_ACTION_PREFIX = "temp_sched_";
@@ -27211,7 +28385,7 @@ registerSchema({
27211
28385
  prompt: { type: "string", description: "Additional prompt instructions for the spawned agent", required: false }
27212
28386
  }
27213
28387
  });
27214
- var createCommand2 = defineCommand145({
28388
+ var createCommand2 = defineCommand147({
27215
28389
  meta: {
27216
28390
  name: "create",
27217
28391
  description: 'Stage a scheduled action. Example: baker scheduled-actions create --name "Weekly report" --description "..." --cron "0 9 * * MON"'
@@ -27260,7 +28434,7 @@ var createCommand2 = defineCommand145({
27260
28434
  });
27261
28435
 
27262
28436
  // src/commands/scheduled-actions/delete.ts
27263
- import { defineCommand as defineCommand146 } from "citty";
28437
+ import { defineCommand as defineCommand148 } from "citty";
27264
28438
  registerSchema({
27265
28439
  command: "scheduled-actions.delete",
27266
28440
  description: "Stage deletion of a published scheduled action or cancellation of a temp_sched_* draft creation.",
@@ -27268,7 +28442,7 @@ registerSchema({
27268
28442
  id: { type: "string", description: "Published scheduled action ID or temp_sched_* draft ID", required: true }
27269
28443
  }
27270
28444
  });
27271
- var deleteCommand2 = defineCommand146({
28445
+ var deleteCommand2 = defineCommand148({
27272
28446
  meta: {
27273
28447
  name: "delete",
27274
28448
  description: "Stage scheduled action deletion. Example: baker scheduled-actions delete <id-or-temp_sched_id>"
@@ -27297,7 +28471,7 @@ var deleteCommand2 = defineCommand146({
27297
28471
  });
27298
28472
 
27299
28473
  // src/commands/scheduled-actions/get.ts
27300
- import { defineCommand as defineCommand147 } from "citty";
28474
+ import { defineCommand as defineCommand149 } from "citty";
27301
28475
  registerSchema({
27302
28476
  command: "scheduled-actions.get",
27303
28477
  description: "Get a published scheduled action or a temp_sched_* draft-created scheduled action.",
@@ -27305,7 +28479,7 @@ registerSchema({
27305
28479
  id: { type: "string", description: "Published scheduled action ID or temp_sched_* draft ID", required: true }
27306
28480
  }
27307
28481
  });
27308
- var getCommand3 = defineCommand147({
28482
+ var getCommand3 = defineCommand149({
27309
28483
  meta: {
27310
28484
  name: "get",
27311
28485
  description: "Get a scheduled action. Example: baker scheduled-actions get <id-or-temp_sched_id>"
@@ -27342,13 +28516,13 @@ var getCommand3 = defineCommand147({
27342
28516
  });
27343
28517
 
27344
28518
  // src/commands/scheduled-actions/list.ts
27345
- import { defineCommand as defineCommand148 } from "citty";
28519
+ import { defineCommand as defineCommand150 } from "citty";
27346
28520
  registerSchema({
27347
28521
  command: "scheduled-actions.list",
27348
28522
  description: "List published scheduled actions. Includes draft state when BAKER_CHAT_ID is set.",
27349
28523
  args: {}
27350
28524
  });
27351
- var listCommand9 = defineCommand148({
28525
+ var listCommand9 = defineCommand150({
27352
28526
  meta: {
27353
28527
  name: "list",
27354
28528
  description: "List scheduled actions. Includes staged draft ops when BAKER_CHAT_ID is set."
@@ -27369,7 +28543,7 @@ var listCommand9 = defineCommand148({
27369
28543
  });
27370
28544
 
27371
28545
  // src/commands/scheduled-actions/trigger.ts
27372
- import { defineCommand as defineCommand149 } from "citty";
28546
+ import { defineCommand as defineCommand151 } from "citty";
27373
28547
  registerSchema({
27374
28548
  command: "scheduled-actions.trigger",
27375
28549
  description: "Immediately trigger a published scheduled action. Does not require BAKER_CHAT_ID and rejects temp_sched_* IDs.",
@@ -27377,7 +28551,7 @@ registerSchema({
27377
28551
  id: { type: "string", description: "Published scheduled action ID", required: true }
27378
28552
  }
27379
28553
  });
27380
- var triggerCommand = defineCommand149({
28554
+ var triggerCommand = defineCommand151({
27381
28555
  meta: {
27382
28556
  name: "trigger",
27383
28557
  description: "Immediately trigger a published scheduled action. Example: baker scheduled-actions trigger <id>"
@@ -27414,7 +28588,7 @@ var triggerCommand = defineCommand149({
27414
28588
  });
27415
28589
 
27416
28590
  // src/commands/scheduled-actions/update.ts
27417
- import { defineCommand as defineCommand150 } from "citty";
28591
+ import { defineCommand as defineCommand152 } from "citty";
27418
28592
  registerSchema({
27419
28593
  command: "scheduled-actions.update",
27420
28594
  description: "Stage an update to a published scheduled action or temp_sched_* draft-created scheduled action.",
@@ -27439,7 +28613,7 @@ registerSchema({
27439
28613
  prompt: { type: "string", description: "Replacement additional spawned-agent instructions", required: false }
27440
28614
  }
27441
28615
  });
27442
- var updateCommand2 = defineCommand150({
28616
+ var updateCommand2 = defineCommand152({
27443
28617
  meta: {
27444
28618
  name: "update",
27445
28619
  description: "Stage a scheduled action update. Example: baker scheduled-actions update <id> --enabled false"
@@ -27510,7 +28684,7 @@ var updateCommand2 = defineCommand150({
27510
28684
  });
27511
28685
 
27512
28686
  // src/commands/scheduled-actions/index.ts
27513
- var scheduledActionsCommand = defineCommand151({
28687
+ var scheduledActionsCommand = defineCommand153({
27514
28688
  meta: {
27515
28689
  name: "scheduled-actions",
27516
28690
  description: `Manage Scheduled Actions. Subcommands: list, get, create, update, delete, trigger.
@@ -27537,8 +28711,8 @@ Full guide: __tooling__/docs/tools/baker/scheduled-actions.md`
27537
28711
  });
27538
28712
 
27539
28713
  // src/commands/schema.ts
27540
- import { defineCommand as defineCommand152 } from "citty";
27541
- var schemaCommand = defineCommand152({
28714
+ import { defineCommand as defineCommand154 } from "citty";
28715
+ var schemaCommand = defineCommand154({
27542
28716
  meta: {
27543
28717
  name: "schema",
27544
28718
  description: "Inspect command argument schemas (for AI agent introspection). Lists all commands if no argument given. Example: baker schema images.search"
@@ -27580,7 +28754,7 @@ var schemaCommand = defineCommand152({
27580
28754
  });
27581
28755
 
27582
28756
  // src/commands/tags/index.ts
27583
- import { defineCommand as defineCommand153 } from "citty";
28757
+ import { defineCommand as defineCommand155 } from "citty";
27584
28758
 
27585
28759
  // src/commands/tags/shared.ts
27586
28760
  function failApi3(err) {
@@ -27646,7 +28820,7 @@ async function listTags(json) {
27646
28820
  failApi3(err);
27647
28821
  }
27648
28822
  }
27649
- var listCommand10 = defineCommand153({
28823
+ var listCommand10 = defineCommand155({
27650
28824
  meta: {
27651
28825
  name: "list",
27652
28826
  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"
@@ -27665,7 +28839,7 @@ async function listDraft3() {
27665
28839
  failApi3(err);
27666
28840
  }
27667
28841
  }
27668
- var draftCommand3 = defineCommand153({
28842
+ var draftCommand3 = defineCommand155({
27669
28843
  meta: {
27670
28844
  name: "draft",
27671
28845
  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)."
@@ -27674,7 +28848,7 @@ var draftCommand3 = defineCommand153({
27674
28848
  await listDraft3();
27675
28849
  }
27676
28850
  });
27677
- var tagsCommand3 = defineCommand153({
28851
+ var tagsCommand3 = defineCommand155({
27678
28852
  meta: {
27679
28853
  name: "tags",
27680
28854
  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.
@@ -27700,10 +28874,10 @@ Full guide: __tooling__/docs/tools/baker/tags.md`
27700
28874
  });
27701
28875
 
27702
28876
  // src/commands/testimonials/index.ts
27703
- import { defineCommand as defineCommand157 } from "citty";
28877
+ import { defineCommand as defineCommand159 } from "citty";
27704
28878
 
27705
28879
  // src/commands/testimonials/get.ts
27706
- import { defineCommand as defineCommand154 } from "citty";
28880
+ import { defineCommand as defineCommand156 } from "citty";
27707
28881
  registerSchema({
27708
28882
  command: "testimonials.get",
27709
28883
  description: "Get a single testimonial by ID",
@@ -27711,7 +28885,7 @@ registerSchema({
27711
28885
  id: { type: "string", description: "Testimonial ID", required: true }
27712
28886
  }
27713
28887
  });
27714
- var getCommand4 = defineCommand154({
28888
+ var getCommand4 = defineCommand156({
27715
28889
  meta: { name: "get", description: "Get a single testimonial by ID. Example: baker testimonials get j571abc123" },
27716
28890
  args: {
27717
28891
  id: { type: "positional", description: "Testimonial ID", required: false },
@@ -27748,7 +28922,7 @@ var getCommand4 = defineCommand154({
27748
28922
  });
27749
28923
 
27750
28924
  // src/commands/testimonials/list.ts
27751
- import { defineCommand as defineCommand155 } from "citty";
28925
+ import { defineCommand as defineCommand157 } from "citty";
27752
28926
  registerSchema({
27753
28927
  command: "testimonials.list",
27754
28928
  description: "List testimonials with optional filters.",
@@ -27778,7 +28952,7 @@ registerSchema({
27778
28952
  limit: { type: "number", description: "Max results (default 50)", required: false, default: 50 }
27779
28953
  }
27780
28954
  });
27781
- var listCommand11 = defineCommand155({
28955
+ var listCommand11 = defineCommand157({
27782
28956
  meta: {
27783
28957
  name: "list",
27784
28958
  description: "List testimonials with optional filters. Example: baker testimonials list --source google --sentiment positive"
@@ -27827,7 +29001,7 @@ var listCommand11 = defineCommand155({
27827
29001
  });
27828
29002
 
27829
29003
  // src/commands/testimonials/search.ts
27830
- import { defineCommand as defineCommand156 } from "citty";
29004
+ import { defineCommand as defineCommand158 } from "citty";
27831
29005
  function languageBiasHint(results, requestedLanguage) {
27832
29006
  if (requestedLanguage) {
27833
29007
  return null;
@@ -27905,7 +29079,7 @@ function buildSearchRequest(query, args) {
27905
29079
  }
27906
29080
  return body;
27907
29081
  }
27908
- var searchCommand2 = defineCommand156({
29082
+ var searchCommand2 = defineCommand158({
27909
29083
  meta: {
27910
29084
  name: "search",
27911
29085
  description: "Semantic search testimonials by text query. Uses hybrid BM25 + vector + reranking. Example: baker testimonials search 'great service' --rating-min 4"
@@ -27961,7 +29135,7 @@ var searchCommand2 = defineCommand156({
27961
29135
  var tagsCommand4 = makeTagsCommand("testimonials", "testimonial", "/api/testimonials/tags");
27962
29136
 
27963
29137
  // src/commands/testimonials/index.ts
27964
- var testimonialsCommand = defineCommand157({
29138
+ var testimonialsCommand = defineCommand159({
27965
29139
  meta: {
27966
29140
  name: "testimonials",
27967
29141
  description: `Find and browse testimonials in Baker. Subcommands: search, get, list, tags.
@@ -27983,10 +29157,10 @@ Full guide: __tooling__/docs/tools/baker/testimonials.md`
27983
29157
  });
27984
29158
 
27985
29159
  // src/commands/videos/index.ts
27986
- import { defineCommand as defineCommand162 } from "citty";
29160
+ import { defineCommand as defineCommand164 } from "citty";
27987
29161
 
27988
29162
  // src/commands/videos/delete.ts
27989
- import { defineCommand as defineCommand158 } from "citty";
29163
+ import { defineCommand as defineCommand160 } from "citty";
27990
29164
  registerSchema({
27991
29165
  command: "videos.delete",
27992
29166
  description: "Delete a video by ID",
@@ -28000,7 +29174,7 @@ registerSchema({
28000
29174
  }
28001
29175
  }
28002
29176
  });
28003
- var deleteCommand3 = defineCommand158({
29177
+ var deleteCommand3 = defineCommand160({
28004
29178
  meta: {
28005
29179
  name: "delete",
28006
29180
  description: "Delete a video by ID. Use --dry-run to preview. Example: baker videos delete j571abc123 --dry-run"
@@ -28041,7 +29215,7 @@ var deleteCommand3 = defineCommand158({
28041
29215
  });
28042
29216
 
28043
29217
  // src/commands/videos/get.ts
28044
- import { defineCommand as defineCommand159 } from "citty";
29218
+ import { defineCommand as defineCommand161 } from "citty";
28045
29219
  registerSchema({
28046
29220
  command: "videos.get",
28047
29221
  description: "Get a single video by ID",
@@ -28049,7 +29223,7 @@ registerSchema({
28049
29223
  id: { type: "string", description: "Video ID", required: true }
28050
29224
  }
28051
29225
  });
28052
- var getCommand5 = defineCommand159({
29226
+ var getCommand5 = defineCommand161({
28053
29227
  meta: { name: "get", description: "Get a single video by ID. Example: baker videos get j571abc123" },
28054
29228
  args: {
28055
29229
  id: { type: "positional", description: "Video ID", required: false },
@@ -28086,7 +29260,7 @@ var getCommand5 = defineCommand159({
28086
29260
  });
28087
29261
 
28088
29262
  // src/commands/videos/search.ts
28089
- import { defineCommand as defineCommand160 } from "citty";
29263
+ import { defineCommand as defineCommand162 } from "citty";
28090
29264
  registerSchema({
28091
29265
  command: "videos.search",
28092
29266
  description: "Search videos by text query. Only returns ready videos.",
@@ -28096,7 +29270,7 @@ registerSchema({
28096
29270
  tags: { type: "string", description: "Comma-separated tags to filter by", required: false }
28097
29271
  }
28098
29272
  });
28099
- var searchCommand3 = defineCommand160({
29273
+ var searchCommand3 = defineCommand162({
28100
29274
  meta: {
28101
29275
  name: "search",
28102
29276
  description: "Semantic search videos by text query. Uses hybrid BM25 + vector + reranking. Example: baker videos search 'product demo' --tags tutorial"
@@ -28146,9 +29320,9 @@ var searchCommand3 = defineCommand160({
28146
29320
  var tagsCommand5 = makeTagsCommand("videos", "video", "/api/videos/tags");
28147
29321
 
28148
29322
  // src/commands/videos/upload.ts
28149
- import { readFile as readFile20, stat as stat5 } from "fs/promises";
29323
+ import { readFile as readFile22, stat as stat7 } from "fs/promises";
28150
29324
  import { extname as extname3 } from "path";
28151
- import { defineCommand as defineCommand161 } from "citty";
29325
+ import { defineCommand as defineCommand163 } from "citty";
28152
29326
  var MIME_MAP = {
28153
29327
  ".mp4": "video/mp4",
28154
29328
  ".mov": "video/quicktime",
@@ -28182,7 +29356,7 @@ function detectContentType(filePath) {
28182
29356
  }
28183
29357
  return mime;
28184
29358
  }
28185
- var uploadCommand2 = defineCommand161({
29359
+ var uploadCommand2 = defineCommand163({
28186
29360
  meta: {
28187
29361
  name: "upload",
28188
29362
  description: "Upload a video file to Baker via Mux direct upload. Auto-detects content type. Example: baker videos upload ./demo.mp4"
@@ -28201,7 +29375,7 @@ var uploadCommand2 = defineCommand161({
28201
29375
  }
28202
29376
  const contentType = args["content-type"] || detectContentType(filePath);
28203
29377
  if (args["dry-run"]) {
28204
- const fileStats = await stat5(filePath);
29378
+ const fileStats = await stat7(filePath);
28205
29379
  writeJson({
28206
29380
  ok: true,
28207
29381
  dryRun: true,
@@ -28211,7 +29385,7 @@ var uploadCommand2 = defineCommand161({
28211
29385
  return;
28212
29386
  }
28213
29387
  const { uploadUrl, videoId } = await apiPost("/api/videos/upload", {});
28214
- const fileBuffer = await readFile20(filePath);
29388
+ const fileBuffer = await readFile22(filePath);
28215
29389
  const uploadResponse = await fetch(uploadUrl, {
28216
29390
  method: "PUT",
28217
29391
  headers: { "Content-Type": contentType },
@@ -28236,7 +29410,7 @@ var uploadCommand2 = defineCommand161({
28236
29410
  });
28237
29411
 
28238
29412
  // src/commands/videos/index.ts
28239
- var videosCommand = defineCommand162({
29413
+ var videosCommand = defineCommand164({
28240
29414
  meta: {
28241
29415
  name: "videos",
28242
29416
  description: `Find and manage videos in Baker. Subcommands: search, get, upload, delete, tags.
@@ -28260,10 +29434,10 @@ Full guide: __tooling__/docs/tools/baker/videos.md`
28260
29434
  });
28261
29435
 
28262
29436
  // src/commands/winning-ads/index.ts
28263
- import { defineCommand as defineCommand174 } from "citty";
29437
+ import { defineCommand as defineCommand176 } from "citty";
28264
29438
 
28265
29439
  // src/commands/winning-ads/advertisers.ts
28266
- import { defineCommand as defineCommand163 } from "citty";
29440
+ import { defineCommand as defineCommand165 } from "citty";
28267
29441
 
28268
29442
  // src/commands/winning-ads/shared.ts
28269
29443
  function splitList(value) {
@@ -28316,7 +29490,7 @@ function advertiserNormalizer(record, full) {
28316
29490
  last_synced_at: record.last_synced_at ?? null
28317
29491
  };
28318
29492
  }
28319
- var advertisersCommand2 = defineCommand163({
29493
+ var advertisersCommand2 = defineCommand165({
28320
29494
  meta: {
28321
29495
  name: "advertisers",
28322
29496
  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'
@@ -28374,7 +29548,7 @@ var advertisersCommand2 = defineCommand163({
28374
29548
  });
28375
29549
 
28376
29550
  // src/commands/winning-ads/brief.ts
28377
- import { defineCommand as defineCommand164 } from "citty";
29551
+ import { defineCommand as defineCommand166 } from "citty";
28378
29552
  registerSchema({
28379
29553
  command: "winning-ads.brief",
28380
29554
  description: "Generate a creative brief grounded in strategically-similar winning ads. Optionally describe the target creative with --dna (JSON) and steer with --notes.",
@@ -28420,7 +29594,7 @@ function parseDna(raw) {
28420
29594
  }
28421
29595
  return parsed;
28422
29596
  }
28423
- var briefCommand = defineCommand164({
29597
+ var briefCommand = defineCommand166({
28424
29598
  meta: {
28425
29599
  name: "brief",
28426
29600
  description: `Generate a creative brief from winning references. Example: baker winning-ads brief --dna '{"angle":"cost savings"}' --notes "B2B, LinkedIn video" --k 8`
@@ -28456,7 +29630,7 @@ var briefCommand = defineCommand164({
28456
29630
  });
28457
29631
 
28458
29632
  // src/commands/winning-ads/feed.ts
28459
- import { defineCommand as defineCommand165 } from "citty";
29633
+ import { defineCommand as defineCommand167 } from "citty";
28460
29634
  function buildFeedParams(input) {
28461
29635
  const params = {};
28462
29636
  const advertiser = splitList(input.advertiser);
@@ -28508,7 +29682,7 @@ registerSchema({
28508
29682
  format: { type: "string", description: "Comma-separated formats to include (e.g. static,video)", required: false }
28509
29683
  }
28510
29684
  });
28511
- var feedCommand = defineCommand165({
29685
+ var feedCommand = defineCommand167({
28512
29686
  meta: {
28513
29687
  name: "feed",
28514
29688
  description: "Winners across every brand you follow (browse, then trim per advertiser). Example: baker winning-ads feed --per-advertiser 5 --output md"
@@ -28593,7 +29767,7 @@ var feedCommand = defineCommand165({
28593
29767
  });
28594
29768
 
28595
29769
  // src/commands/winning-ads/follow.ts
28596
- import { defineCommand as defineCommand166 } from "citty";
29770
+ import { defineCommand as defineCommand168 } from "citty";
28597
29771
  var PLATFORMS = ["meta", "linkedin"];
28598
29772
  registerSchema({
28599
29773
  command: "winning-ads.follow",
@@ -28608,7 +29782,7 @@ registerSchema({
28608
29782
  label: { type: "string", description: "Optional display label (defaults to the resolved name)", required: false }
28609
29783
  }
28610
29784
  });
28611
- var followCommand = defineCommand166({
29785
+ var followCommand = defineCommand168({
28612
29786
  meta: {
28613
29787
  name: "follow",
28614
29788
  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'
@@ -28655,7 +29829,7 @@ var followCommand = defineCommand166({
28655
29829
  });
28656
29830
 
28657
29831
  // src/commands/winning-ads/follow-competitors.ts
28658
- import { defineCommand as defineCommand167 } from "citty";
29832
+ import { defineCommand as defineCommand169 } from "citty";
28659
29833
  var PLATFORMS2 = ["meta", "linkedin"];
28660
29834
  var BATCH_TIMEOUT_MS = 3e5;
28661
29835
  function buildFollowBatchBody(input) {
@@ -28688,7 +29862,7 @@ registerSchema({
28688
29862
  }
28689
29863
  }
28690
29864
  });
28691
- var followCompetitorsCommand = defineCommand167({
29865
+ var followCompetitorsCommand = defineCommand169({
28692
29866
  meta: {
28693
29867
  name: "follow-competitors",
28694
29868
  description: 'Follow many brands at once by domain \u2014 add every competitor in one call. Example: baker winning-ads follow-competitors "deel.com,notion.so,hubspot.com"'
@@ -28763,7 +29937,7 @@ var followCompetitorsCommand = defineCommand167({
28763
29937
  });
28764
29938
 
28765
29939
  // src/commands/winning-ads/following.ts
28766
- import { defineCommand as defineCommand168 } from "citty";
29940
+ import { defineCommand as defineCommand170 } from "citty";
28767
29941
  registerSchema({
28768
29942
  command: "winning-ads.following",
28769
29943
  description: "List the brands you follow in your ad-dna library, with each one's status (ready vs still adding) and cached ad counts.",
@@ -28796,7 +29970,7 @@ function followingNormalizer(record, full) {
28796
29970
  platforms: Array.isArray(record.platforms) ? record.platforms : []
28797
29971
  };
28798
29972
  }
28799
- var followingCommand = defineCommand168({
29973
+ var followingCommand = defineCommand170({
28800
29974
  meta: {
28801
29975
  name: "following",
28802
29976
  description: "List brands you follow, with status (ready / adding\u2026) and cached counts. Example: baker winning-ads following --output md"
@@ -28831,7 +30005,7 @@ var followingCommand = defineCommand168({
28831
30005
  });
28832
30006
 
28833
30007
  // src/commands/winning-ads/patterns.ts
28834
- import { defineCommand as defineCommand169 } from "citty";
30008
+ import { defineCommand as defineCommand171 } from "citty";
28835
30009
  registerSchema({
28836
30010
  command: "winning-ads.patterns",
28837
30011
  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.",
@@ -28870,7 +30044,7 @@ function discriminatorRow(record) {
28870
30044
  top_values_duds: Array.isArray(record.top_values_b) ? record.top_values_b.join(", ") : ""
28871
30045
  };
28872
30046
  }
28873
- var patternsCommand = defineCommand169({
30047
+ var patternsCommand = defineCommand171({
28874
30048
  meta: {
28875
30049
  name: "patterns",
28876
30050
  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"
@@ -28926,7 +30100,7 @@ var patternsCommand = defineCommand169({
28926
30100
  });
28927
30101
 
28928
30102
  // src/commands/winning-ads/search.ts
28929
- import { defineCommand as defineCommand170 } from "citty";
30103
+ import { defineCommand as defineCommand172 } from "citty";
28930
30104
  registerSchema({
28931
30105
  command: "winning-ads.search",
28932
30106
  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.",
@@ -29034,7 +30208,7 @@ function buildSearchBody(args) {
29034
30208
  }
29035
30209
  return body;
29036
30210
  }
29037
- var searchCommand4 = defineCommand170({
30211
+ var searchCommand4 = defineCommand172({
29038
30212
  meta: {
29039
30213
  name: "search",
29040
30214
  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"
@@ -29149,7 +30323,7 @@ var searchCommand4 = defineCommand170({
29149
30323
  });
29150
30324
 
29151
30325
  // src/commands/winning-ads/seeds.ts
29152
- import { defineCommand as defineCommand171 } from "citty";
30326
+ import { defineCommand as defineCommand173 } from "citty";
29153
30327
  function leanRow(r) {
29154
30328
  return {
29155
30329
  key: r.key,
@@ -29177,7 +30351,7 @@ function makeSeedCommand(opts) {
29177
30351
  limit: { type: "number", description: "Max keys 1-100 (default 20)", required: false, default: 20 }
29178
30352
  }
29179
30353
  });
29180
- return defineCommand171({
30354
+ return defineCommand173({
29181
30355
  meta: { name: opts.name, description: opts.description },
29182
30356
  args: {
29183
30357
  platform: { type: "string", description: "Single platform to segment on", required: false },
@@ -29226,7 +30400,7 @@ var formatsCommand = makeSeedCommand({
29226
30400
  });
29227
30401
 
29228
30402
  // src/commands/winning-ads/unfollow.ts
29229
- import { defineCommand as defineCommand172 } from "citty";
30403
+ import { defineCommand as defineCommand174 } from "citty";
29230
30404
  registerSchema({
29231
30405
  command: "winning-ads.unfollow",
29232
30406
  description: "Stop following a brand \u2014 removes it from your ad-dna library by advertiser id.",
@@ -29234,7 +30408,7 @@ registerSchema({
29234
30408
  advertiser: { type: "string", description: "Advertiser id to unfollow", required: true }
29235
30409
  }
29236
30410
  });
29237
- var unfollowCommand = defineCommand172({
30411
+ var unfollowCommand = defineCommand174({
29238
30412
  meta: {
29239
30413
  name: "unfollow",
29240
30414
  description: "Stop following a brand by advertiser id. Example: baker winning-ads unfollow adv_123"
@@ -29255,7 +30429,7 @@ var unfollowCommand = defineCommand172({
29255
30429
  });
29256
30430
 
29257
30431
  // src/commands/winning-ads/winners.ts
29258
- import { defineCommand as defineCommand173 } from "citty";
30432
+ import { defineCommand as defineCommand175 } from "citty";
29259
30433
  registerSchema({
29260
30434
  command: "winning-ads.winners",
29261
30435
  description: "Top winning ads for one advertiser id (from `advertisers` or `following`). Returns lean winner cards; add --full for DNA + longevity.",
@@ -29265,7 +30439,7 @@ registerSchema({
29265
30439
  platform: { type: "string", description: "Filter to a single platform: meta|linkedin", required: false }
29266
30440
  }
29267
30441
  });
29268
- var winnersCommand = defineCommand173({
30442
+ var winnersCommand = defineCommand175({
29269
30443
  meta: {
29270
30444
  name: "winners",
29271
30445
  description: "Top winning ads for a specific advertiser id. Example: baker winning-ads winners adv_123 --top 15 --output md"
@@ -29315,7 +30489,7 @@ var winnersCommand = defineCommand173({
29315
30489
  });
29316
30490
 
29317
30491
  // src/commands/winning-ads/index.ts
29318
- var winningAdsCommand = defineCommand174({
30492
+ var winningAdsCommand = defineCommand176({
29319
30493
  meta: {
29320
30494
  name: "winning-ads",
29321
30495
  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.
@@ -29384,7 +30558,7 @@ function getCliVersion() {
29384
30558
  }
29385
30559
 
29386
30560
  // src/cli.ts
29387
- var main = defineCommand175({
30561
+ var main = defineCommand177({
29388
30562
  meta: {
29389
30563
  name: "baker",
29390
30564
  version: getCliVersion(),
@@ -29406,6 +30580,7 @@ Introspection: Run 'baker schema <command>' to inspect argument schemas.`
29406
30580
  creatives: creativesCommand3,
29407
30581
  flows: flowsCommand,
29408
30582
  images: imagesCommand,
30583
+ landing: landingCommand,
29409
30584
  videos: videosCommand,
29410
30585
  testimonials: testimonialsCommand,
29411
30586
  canvas: canvasCommand,