@koda-sl/baker-cli 0.243.0 → 0.245.0-dev.72ca35142

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
@@ -58,7 +58,7 @@ import {
58
58
  ulid,
59
59
  validateCanvasDeep,
60
60
  ytDlpBlockSignal
61
- } from "./chunk-EKLAHWSF.js";
61
+ } from "./chunk-CMPAHYLB.js";
62
62
  import {
63
63
  csvOrJson,
64
64
  daysAgoIso,
@@ -108,7 +108,7 @@ import {
108
108
  } from "./chunk-DZUVUGEP.js";
109
109
 
110
110
  // src/cli.ts
111
- import { defineCommand as defineCommand227, runMain } from "citty";
111
+ import { defineCommand as defineCommand229, runMain } from "citty";
112
112
 
113
113
  // src/cache-flag.ts
114
114
  var NO_CACHE_ARG = {
@@ -2122,18 +2122,6 @@ var BIDDING_STRATEGY_TYPES = [
2122
2122
  "MANUAL_CPV",
2123
2123
  "PERCENT_CPC"
2124
2124
  ];
2125
- var MERGED_TARGET_STRATEGY_CHANNELS = /* @__PURE__ */ new Set(["SEARCH"]);
2126
- var MERGED_TARGET_CPA_MESSAGE = "Google doesn't accept Target CPA as a bidding strategy of its own on a Search campaign \u2014 the target sits inside Maximize conversions. Choose Maximize conversions and give it the same target CPA; it bids exactly the same.";
2127
- var MERGED_TARGET_ROAS_MESSAGE = "Google doesn't accept Target ROAS as a bidding strategy of its own on a Search campaign \u2014 the target sits inside Maximize conversion value. Choose Maximize conversion value and give it the same target ROAS; it bids exactly the same.";
2128
- function mergedTargetStrategyRefusal(channelType, biddingType) {
2129
- if (channelType === void 0 || !MERGED_TARGET_STRATEGY_CHANNELS.has(channelType)) {
2130
- return void 0;
2131
- }
2132
- if (biddingType === "TARGET_CPA") {
2133
- return MERGED_TARGET_CPA_MESSAGE;
2134
- }
2135
- return biddingType === "TARGET_ROAS" ? MERGED_TARGET_ROAS_MESSAGE : void 0;
2136
- }
2137
2125
  var POSITIVE_GEO_TARGET_TYPES = ["PRESENCE_OR_INTEREST", "PRESENCE"];
2138
2126
  var NEGATIVE_GEO_TARGET_TYPES = ["PRESENCE_OR_INTEREST", "PRESENCE"];
2139
2127
  var BUDGET_DELIVERY_METHODS = ["STANDARD", "ACCELERATED"];
@@ -2858,10 +2846,6 @@ var campaignCreateSchema2 = z4.strictObject({
2858
2846
  if (p.channelType === "PERFORMANCE_MAX" && p.bidding && p.bidding.type === "MANUAL_CPC") {
2859
2847
  ctx.addIssue({ code: "custom", path: ["bidding"], message: "Performance Max does not support Manual CPC" });
2860
2848
  }
2861
- const mergedTarget = mergedTargetStrategyRefusal(p.channelType, p.bidding?.type);
2862
- if (mergedTarget) {
2863
- ctx.addIssue({ code: "custom", path: ["bidding"], message: mergedTarget });
2864
- }
2865
2849
  if (p.startDate && p.endDate && p.endDate <= p.startDate) {
2866
2850
  ctx.addIssue({ code: "custom", path: ["endDate"], message: "endDate must be after startDate" });
2867
2851
  }
@@ -3936,10 +3920,35 @@ var capabilitySurfaceReportSchema = z9.object({
3936
3920
  /** Where to look next: `baker ads google`, `baker ga4`, … */
3937
3921
  commands: z9.string()
3938
3922
  });
3923
+ var capabilityToolSchema = z9.object({
3924
+ /** `microsoft_clarity`, `hubspot`, or the name the user gave a custom server. */
3925
+ key: z9.string(),
3926
+ label: z9.string(),
3927
+ /** `mcp` — tools in the agent's own list. `platform` — signed in, read through the CLI. */
3928
+ kind: z9.enum(["mcp", "platform"]),
3929
+ /** How to actually reach it, in one phrase the agent can act on. */
3930
+ reach: z9.string(),
3931
+ /**
3932
+ * Whether it can be used right now.
3933
+ *
3934
+ * A connection that exists and is expired is worse than one that is absent:
3935
+ * it reads as available and fails at the moment it is needed, which on an
3936
+ * unattended run is a report with a hole in it and no explanation.
3937
+ */
3938
+ ready: z9.boolean(),
3939
+ /** Why it is not ready, or what it is for. Absent when neither needs saying. */
3940
+ note: z9.string().optional()
3941
+ });
3939
3942
  var capabilitiesResponseSchema = z9.object({
3940
3943
  ok: z9.literal(true),
3941
3944
  data: z9.object({
3942
- surfaces: z9.array(capabilitySurfaceReportSchema)
3945
+ surfaces: z9.array(capabilitySurfaceReportSchema),
3946
+ /**
3947
+ * Everything else this company can reach. Shared connections only —
3948
+ * a teammate's private per-user connection belongs to whoever started the
3949
+ * session and is visible in the agent's own tool list, not here.
3950
+ */
3951
+ tools: z9.array(capabilityToolSchema)
3943
3952
  })
3944
3953
  });
3945
3954
 
@@ -3986,6 +3995,8 @@ var chatChangeTypeSchema = z10.enum([
3986
3995
  // what the edge captures and one held until Publish would not measure the
3987
3996
  // campaign it was written for.
3988
3997
  "analytics-mapping",
3998
+ // An A/B test between a landing and its alternative. Staged, not immediate.
3999
+ "experiment",
3989
4000
  "briefs"
3990
4001
  ]);
3991
4002
  var chatChangeActionSchema = z10.enum(["created", "updated", "deleted"]);
@@ -9779,11 +9790,11 @@ function rawTextEntries(value) {
9779
9790
  const values = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
9780
9791
  return values.filter((v) => typeof v === "string").flatMap((v) => v.split(",")).map((v) => v.trim()).filter(Boolean);
9781
9792
  }
9782
- function rawFileEntries(path40) {
9783
- if (typeof path40 !== "string" || path40.length === 0) {
9793
+ function rawFileEntries(path43) {
9794
+ if (typeof path43 !== "string" || path43.length === 0) {
9784
9795
  return [];
9785
9796
  }
9786
- return readFileSync2(path40, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
9797
+ return readFileSync2(path43, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
9787
9798
  }
9788
9799
  function keywordEntries(args) {
9789
9800
  const defaultMatch = typeof args["match-type"] === "string" ? args["match-type"].toUpperCase() : void 0;
@@ -9806,19 +9817,19 @@ function keywordEntries(args) {
9806
9817
  }
9807
9818
  return entries;
9808
9819
  }
9809
- function loadJsonFileArg(path40) {
9810
- if (typeof path40 !== "string" || path40.length === 0) {
9820
+ function loadJsonFileArg(path43) {
9821
+ if (typeof path43 !== "string" || path43.length === 0) {
9811
9822
  return {};
9812
9823
  }
9813
9824
  try {
9814
- const parsed = JSON.parse(readFileSync2(path40, "utf8"));
9825
+ const parsed = JSON.parse(readFileSync2(path43, "utf8"));
9815
9826
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
9816
- failWriteValidation(`${path40} must contain a JSON object`);
9827
+ failWriteValidation(`${path43} must contain a JSON object`);
9817
9828
  }
9818
9829
  return parsed;
9819
9830
  } catch (err) {
9820
9831
  if (err instanceof SyntaxError) {
9821
- failWriteValidation(`${path40} is not valid JSON: ${err.message}`);
9832
+ failWriteValidation(`${path43} is not valid JSON: ${err.message}`);
9822
9833
  }
9823
9834
  throw err;
9824
9835
  }
@@ -9948,10 +9959,10 @@ async function stageUpdate(kind, customerId, target, payload, hints) {
9948
9959
  async function stageTarget(kind, customerId, target, hints) {
9949
9960
  await stageGoogleOp({ kind, customerId, target }, hints);
9950
9961
  }
9951
- async function draftAction(path40, body, chat) {
9962
+ async function draftAction(path43, body, chat) {
9952
9963
  try {
9953
9964
  const chatId = resolveChatId(chat);
9954
- const response = await apiPost(path40, { chatId, ...body });
9965
+ const response = await apiPost(path43, { chatId, ...body });
9955
9966
  writeJsonEnvelope(response);
9956
9967
  } catch (err) {
9957
9968
  handleGoogleError(err);
@@ -13644,16 +13655,10 @@ var campaignsCommand = defineCommand30({
13644
13655
  "budget-ref": { type: "string", description: "Budget ref (g_temp_*, id, or resource name)" },
13645
13656
  "bidding-strategy": {
13646
13657
  type: "string",
13647
- description: "MANUAL_CPC | MAXIMIZE_CONVERSIONS | MAXIMIZE_CONVERSION_VALUE | TARGET_SPEND | \u2026"
13648
- },
13649
- "target-cpa": {
13650
- type: "string",
13651
- description: "Target CPA (major units). On a Search campaign pair it with --bidding-strategy MAXIMIZE_CONVERSIONS \u2014 Google keeps the target inside that strategy and rejects a standalone TARGET_CPA."
13652
- },
13653
- "target-roas": {
13654
- type: "string",
13655
- description: "Target ROAS (e.g. 4.0). On a Search campaign pair it with --bidding-strategy MAXIMIZE_CONVERSION_VALUE \u2014 Google keeps the target inside that strategy and rejects a standalone TARGET_ROAS."
13658
+ description: "MANUAL_CPC | MAXIMIZE_CONVERSIONS | TARGET_CPA | TARGET_ROAS | \u2026"
13656
13659
  },
13660
+ "target-cpa": { type: "string", description: "Target CPA (major units) for TARGET_CPA" },
13661
+ "target-roas": { type: "string", description: "Target ROAS (e.g. 4.0) for TARGET_ROAS" },
13657
13662
  ...maxCpcArg,
13658
13663
  ...finalUrlSuffixArg,
13659
13664
  ...urlOptionsArgs,
@@ -13700,18 +13705,9 @@ var campaignsCommand = defineCommand30({
13700
13705
  ...fileArg,
13701
13706
  name: { type: "string" },
13702
13707
  "budget-ref": { type: "string" },
13703
- "bidding-strategy": {
13704
- type: "string",
13705
- description: "MANUAL_CPC | MAXIMIZE_CONVERSIONS | MAXIMIZE_CONVERSION_VALUE | TARGET_SPEND | \u2026"
13706
- },
13707
- "target-cpa": {
13708
- type: "string",
13709
- description: "Target CPA (major units). On a Search campaign pair it with --bidding-strategy MAXIMIZE_CONVERSIONS \u2014 Google keeps the target inside that strategy and rejects a standalone TARGET_CPA."
13710
- },
13711
- "target-roas": {
13712
- type: "string",
13713
- description: "Target ROAS (e.g. 4.0). On a Search campaign pair it with --bidding-strategy MAXIMIZE_CONVERSION_VALUE \u2014 Google keeps the target inside that strategy and rejects a standalone TARGET_ROAS."
13714
- },
13708
+ "bidding-strategy": { type: "string" },
13709
+ "target-cpa": { type: "string" },
13710
+ "target-roas": { type: "string" },
13715
13711
  ...maxCpcArg,
13716
13712
  ...finalUrlSuffixArg,
13717
13713
  ...urlOptionsArgs,
@@ -14915,19 +14911,19 @@ function failWriteValidation2(message) {
14915
14911
  writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
14916
14912
  process.exit(1);
14917
14913
  }
14918
- function loadJsonFileArg2(path40) {
14919
- if (typeof path40 !== "string" || path40.length === 0) {
14914
+ function loadJsonFileArg2(path43) {
14915
+ if (typeof path43 !== "string" || path43.length === 0) {
14920
14916
  return {};
14921
14917
  }
14922
14918
  try {
14923
- const parsed = JSON.parse(readFileSync4(path40, "utf8"));
14919
+ const parsed = JSON.parse(readFileSync4(path43, "utf8"));
14924
14920
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
14925
- failWriteValidation2(`${path40} must contain a JSON object`);
14921
+ failWriteValidation2(`${path43} must contain a JSON object`);
14926
14922
  }
14927
14923
  return parsed;
14928
14924
  } catch (err) {
14929
14925
  if (err instanceof SyntaxError) {
14930
- failWriteValidation2(`${path40} is not valid JSON: ${err.message}`);
14926
+ failWriteValidation2(`${path43} is not valid JSON: ${err.message}`);
14931
14927
  }
14932
14928
  throw err;
14933
14929
  }
@@ -15012,15 +15008,15 @@ function parseLocaleFlag(value) {
15012
15008
  }
15013
15009
  return { language: match[1], country: match[2].toUpperCase() };
15014
15010
  }
15015
- function loadTargetingFileArg(path40) {
15016
- if (typeof path40 !== "string" || path40.length === 0) {
15011
+ function loadTargetingFileArg(path43) {
15012
+ if (typeof path43 !== "string" || path43.length === 0) {
15017
15013
  return void 0;
15018
15014
  }
15019
- const parsed = loadJsonFileArg2(path40);
15015
+ const parsed = loadJsonFileArg2(path43);
15020
15016
  const criteria = parsed.targetingCriteria ?? parsed;
15021
15017
  if (!criteria.include) {
15022
15018
  failWriteValidation2(
15023
- `${path40} must contain targeting criteria with an "include" block (see baker schema ads.linkedin.campaigns.create)`
15019
+ `${path43} must contain targeting criteria with an "include" block (see baker schema ads.linkedin.campaigns.create)`
15024
15020
  );
15025
15021
  }
15026
15022
  return criteria;
@@ -15055,14 +15051,14 @@ function parseCsvLine(line) {
15055
15051
  cells.push(current);
15056
15052
  return cells.map((cell2) => cell2.trim());
15057
15053
  }
15058
- function parseListFileArg(path40, maxRows) {
15059
- if (typeof path40 !== "string" || path40.length === 0) {
15054
+ function parseListFileArg(path43, maxRows) {
15055
+ if (typeof path43 !== "string" || path43.length === 0) {
15060
15056
  return void 0;
15061
15057
  }
15062
- const raw = readFileSync4(path40, "utf8");
15058
+ const raw = readFileSync4(path43, "utf8");
15063
15059
  const lines = raw.split(/\r?\n/).filter((line) => line.trim().length > 0);
15064
15060
  if (lines.length < 2) {
15065
- failWriteValidation2(`${path40} needs a header row and at least one data row`);
15061
+ failWriteValidation2(`${path43} needs a header row and at least one data row`);
15066
15062
  }
15067
15063
  const columns = parseCsvLine(lines[0]).map((column) => column.trim());
15068
15064
  const rows = [];
@@ -15081,7 +15077,7 @@ function parseListFileArg(path40, maxRows) {
15081
15077
  }
15082
15078
  }
15083
15079
  if (rows.length > maxRows) {
15084
- failWriteValidation2(`${path40} has ${rows.length} rows \u2014 the inline limit is ${maxRows}. Split the list.`);
15080
+ failWriteValidation2(`${path43} has ${rows.length} rows \u2014 the inline limit is ${maxRows}. Split the list.`);
15085
15081
  }
15086
15082
  return { columns, rows };
15087
15083
  }
@@ -15177,11 +15173,11 @@ function readPositionals(args) {
15177
15173
  function splitIdList(raw) {
15178
15174
  return raw.split(",").map((id) => id.trim()).filter(Boolean);
15179
15175
  }
15180
- function idsFileEntries(path40) {
15181
- if (typeof path40 !== "string" || path40.length === 0) {
15176
+ function idsFileEntries(path43) {
15177
+ if (typeof path43 !== "string" || path43.length === 0) {
15182
15178
  return [];
15183
15179
  }
15184
- return readFileSync4(path40, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).flatMap(splitIdList);
15180
+ return readFileSync4(path43, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).flatMap(splitIdList);
15185
15181
  }
15186
15182
  function requireTargets(args, entity) {
15187
15183
  const positionals = readPositionals(args);
@@ -17802,9 +17798,9 @@ function compactRow(row) {
17802
17798
  ...destination.postUrn ? { postUrn: destination.postUrn } : {}
17803
17799
  };
17804
17800
  }
17805
- function readPath(row, path40) {
17801
+ function readPath(row, path43) {
17806
17802
  let current = row;
17807
- for (const segment of path40.split(".")) {
17803
+ for (const segment of path43.split(".")) {
17808
17804
  const record = asRecord2(current);
17809
17805
  if (!record) return void 0;
17810
17806
  current = record[segment];
@@ -17814,10 +17810,10 @@ function readPath(row, path40) {
17814
17810
  function projectFields(rows, paths) {
17815
17811
  return rows.map((row) => {
17816
17812
  const projected = {};
17817
- for (const path40 of paths) {
17818
- const value = readPath(row, path40);
17813
+ for (const path43 of paths) {
17814
+ const value = readPath(row, path43);
17819
17815
  if (value !== void 0) {
17820
- projected[path40] = value;
17816
+ projected[path43] = value;
17821
17817
  }
17822
17818
  }
17823
17819
  return projected;
@@ -19117,11 +19113,11 @@ var updateStatusSchema = z25.enum(UPDATE_STATUSES);
19117
19113
  function currencyMinimums2(currencyCode) {
19118
19114
  return CURRENCY_MINIMUMS2[currencyCode] ?? DEFAULT_CURRENCY_MINIMUM2;
19119
19115
  }
19120
- function validateDailyBudgetFloor(money, ctx, path40) {
19116
+ function validateDailyBudgetFloor(money, ctx, path43) {
19121
19117
  if (money?.currencyCode) {
19122
19118
  const min = currencyMinimums2(money.currencyCode).dailyBudgetMin;
19123
19119
  if (Number(money.amount) < min) {
19124
- ctx.addIssue({ code: "custom", path: path40, message: `below the ${min} ${money.currencyCode} daily minimum` });
19120
+ ctx.addIssue({ code: "custom", path: path43, message: `below the ${min} ${money.currencyCode} daily minimum` });
19125
19121
  }
19126
19122
  }
19127
19123
  }
@@ -19790,19 +19786,19 @@ function failWriteValidation3(message) {
19790
19786
  writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
19791
19787
  process.exit(1);
19792
19788
  }
19793
- function loadJsonFileArg3(path40) {
19794
- if (typeof path40 !== "string" || path40.length === 0) {
19789
+ function loadJsonFileArg3(path43) {
19790
+ if (typeof path43 !== "string" || path43.length === 0) {
19795
19791
  return {};
19796
19792
  }
19797
19793
  try {
19798
- const parsed = JSON.parse(readFileSync8(path40, "utf8"));
19794
+ const parsed = JSON.parse(readFileSync8(path43, "utf8"));
19799
19795
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
19800
- failWriteValidation3(`${path40} must contain a JSON object`);
19796
+ failWriteValidation3(`${path43} must contain a JSON object`);
19801
19797
  }
19802
19798
  return parsed;
19803
19799
  } catch (err) {
19804
19800
  if (err instanceof SyntaxError) {
19805
- failWriteValidation3(`${path40} is not valid JSON: ${err.message}`);
19801
+ failWriteValidation3(`${path43} is not valid JSON: ${err.message}`);
19806
19802
  }
19807
19803
  throw err;
19808
19804
  }
@@ -24400,11 +24396,11 @@ function unwrap(response) {
24400
24396
  }
24401
24397
  return response.data;
24402
24398
  }
24403
- async function readAvatars(path40, params) {
24404
- return unwrap(await apiGet(path40, params));
24399
+ async function readAvatars(path43, params) {
24400
+ return unwrap(await apiGet(path43, params));
24405
24401
  }
24406
- async function writeAvatars(path40, body) {
24407
- return unwrap(await apiPost(path40, body));
24402
+ async function writeAvatars(path43, body) {
24403
+ return unwrap(await apiPost(path43, body));
24408
24404
  }
24409
24405
 
24410
24406
  // src/commands/avatars/create.ts
@@ -25238,12 +25234,12 @@ function missingFontFiles(urls, available) {
25238
25234
  function planFontAdoption(sources, families) {
25239
25235
  const wanted = new Map(families.map((family) => [normalizeFamily(family), family]));
25240
25236
  const byFamily = /* @__PURE__ */ new Map();
25241
- for (const { path: path40, source } of sources) {
25242
- const dir = posix.dirname(path40);
25237
+ for (const { path: path43, source } of sources) {
25238
+ const dir = posix.dirname(path43);
25243
25239
  for (const face of declaredFontFaces(source)) {
25244
25240
  if (!wanted.has(face.family)) continue;
25245
25241
  const perFile = byFamily.get(face.family) ?? /* @__PURE__ */ new Map();
25246
- perFile.set(path40, [...perFile.get(path40) ?? [], rebaseFontFaceSrc(face.block, dir)]);
25242
+ perFile.set(path43, [...perFile.get(path43) ?? [], rebaseFontFaceSrc(face.block, dir)]);
25247
25243
  byFamily.set(face.family, perFile);
25248
25244
  }
25249
25245
  }
@@ -26267,10 +26263,10 @@ import { defineCommand as defineCommand100 } from "citty";
26267
26263
  import { existsSync as existsSync3, realpathSync } from "fs";
26268
26264
  import { writeFile as writeFile2 } from "fs/promises";
26269
26265
  import path5 from "path";
26270
- function findWorkspaceRoot(startDir, exists = existsSync3, maxDepth = 12) {
26266
+ function findWorkspaceRoot(startDir, exists2 = existsSync3, maxDepth = 12) {
26271
26267
  let dir = path5.resolve(startDir);
26272
26268
  for (let i = 0; i < maxDepth; i++) {
26273
- if (exists(path5.join(dir, "package.json"))) return dir;
26269
+ if (exists2(path5.join(dir, "package.json"))) return dir;
26274
26270
  const parent = path5.dirname(dir);
26275
26271
  if (parent === dir) break;
26276
26272
  dir = parent;
@@ -30217,10 +30213,10 @@ function runDirsToPrune(entries, keep, currentRunId) {
30217
30213
  return runs.slice(0, Math.max(0, runs.length - keep));
30218
30214
  }
30219
30215
  async function pruneOldRuns(outputsDir, keep, currentRunId, log) {
30220
- const { readdir: readdir10 } = await import("fs/promises");
30216
+ const { readdir: readdir11 } = await import("fs/promises");
30221
30217
  let entries;
30222
30218
  try {
30223
- entries = await readdir10(outputsDir);
30219
+ entries = await readdir11(outputsDir);
30224
30220
  } catch {
30225
30221
  return;
30226
30222
  }
@@ -32012,12 +32008,12 @@ async function detectSceneCutsPySceneDetect(filePath, opts = {}) {
32012
32008
  // src/commands/canvas/composition-path.ts
32013
32009
  import { existsSync as existsSync4 } from "fs";
32014
32010
  import path21 from "path";
32015
- function resolveShippedCanvasDir(name, startDir, exists = existsSync4, maxDepth = 8) {
32011
+ function resolveShippedCanvasDir(name, startDir, exists2 = existsSync4, maxDepth = 8) {
32016
32012
  const rel = path21.join("canvas", name);
32017
32013
  let dir = startDir;
32018
32014
  for (let i = 0; i < maxDepth; i++) {
32019
32015
  const candidate = path21.join(dir, rel);
32020
- if (exists(path21.join(candidate, "meta.json"))) return candidate;
32016
+ if (exists2(path21.join(candidate, "meta.json"))) return candidate;
32021
32017
  const parent = path21.dirname(dir);
32022
32018
  if (parent === dir) break;
32023
32019
  dir = parent;
@@ -32843,7 +32839,7 @@ registerSchema({
32843
32839
  }
32844
32840
  }
32845
32841
  });
32846
- function capabilityHints(surfaces) {
32842
+ function capabilityHints(surfaces, tools = []) {
32847
32843
  const hints = [];
32848
32844
  const immediate = surfaces.filter((surface) => surface.writeMode === "applies-at-publish");
32849
32845
  if (immediate.length > 0) {
@@ -32866,6 +32862,18 @@ function capabilityHints(surfaces) {
32866
32862
  "Naming one surface (`baker capabilities google-ads`) adds `ops` \u2014 the exact fields and accepted values of every change there. Read that instead of guessing a field name."
32867
32863
  );
32868
32864
  }
32865
+ const usable = tools.filter((tool) => tool.ready);
32866
+ if (usable.length > 0) {
32867
+ hints.push(
32868
+ `ALSO AVAILABLE, and not on any surface above: ${usable.map((tool) => tool.label).join(", ")}. These answer WHY a page behaves as it does, which no surface here can. See \`tools[].reach\` for how to call each one.`
32869
+ );
32870
+ }
32871
+ const broken = tools.filter((tool) => !tool.ready);
32872
+ if (broken.length > 0) {
32873
+ hints.push(
32874
+ `CONNECTED BUT UNUSABLE: ${broken.map((tool) => `${tool.label} \u2014 ${tool.note ?? "not reachable"}`).join(" ")} Tell the user rather than planning around it silently.`
32875
+ );
32876
+ }
32869
32877
  return hints;
32870
32878
  }
32871
32879
  var runCapabilities = defineCommand108({
@@ -32875,6 +32883,8 @@ var runCapabilities = defineCommand108({
32875
32883
 
32876
32884
  Reports, per surface: what is connected and with what access, what a write there does when it lands, the entities that can be changed, and \u2014 the part nothing else tells you \u2014 what cannot be changed and why.
32877
32885
 
32886
+ Also returns \`tools\`: everything else this company can reach that is not one of those surfaces \u2014 Microsoft Clarity and any other MCP toolkit, custom servers, HubSpot. This is the one call that answers "what do I actually have".
32887
+
32878
32888
  Examples:
32879
32889
  baker capabilities
32880
32890
  baker capabilities google-ads
@@ -32910,7 +32920,7 @@ Full guide: __tooling__/docs/tools/baker/capabilities.md`
32910
32920
  body.full = true;
32911
32921
  }
32912
32922
  const response = await apiPost("/api/capabilities", body);
32913
- const hints = capabilityHints(response.data.surfaces);
32923
+ const hints = capabilityHints(response.data.surfaces, response.data.tools);
32914
32924
  writeJson({
32915
32925
  ...response,
32916
32926
  meta: { count: response.data.surfaces.length },
@@ -33380,12 +33390,793 @@ Full guide: __tooling__/docs/tools/baker/creatives.md`
33380
33390
  }
33381
33391
  });
33382
33392
 
33393
+ // src/commands/experiment/index.ts
33394
+ import { defineCommand as defineCommand112 } from "citty";
33395
+
33396
+ // src/commands/landing/import-graph.ts
33397
+ import { readdir as readdir8, readFile as readFile20, stat as stat4 } from "fs/promises";
33398
+ import path26 from "path";
33399
+ var SOURCE_EXT = /\.(astro|ts|tsx|js|jsx|mjs|cjs|vue|svelte)$/;
33400
+ var RESOLVE_CANDIDATES = [
33401
+ "",
33402
+ ".astro",
33403
+ ".ts",
33404
+ ".tsx",
33405
+ ".js",
33406
+ ".jsx",
33407
+ ".mjs",
33408
+ ".cjs",
33409
+ "/index.astro",
33410
+ "/index.ts",
33411
+ "/index.js"
33412
+ ];
33413
+ var IMPORT_RE = /(?<![A-Za-z0-9_$])(?:import|export)\s*(?:[\s\S]*?\bfrom\s*)?["']([^"']+)["']/g;
33414
+ var DYNAMIC_IMPORT_RE = /\bimport\s*\(\s*["']([^"']+)["']\s*\)/g;
33415
+ async function isFile(p) {
33416
+ try {
33417
+ return (await stat4(p)).isFile();
33418
+ } catch {
33419
+ return false;
33420
+ }
33421
+ }
33422
+ async function walkFiles(dir) {
33423
+ let entries;
33424
+ try {
33425
+ entries = await readdir8(dir, { withFileTypes: true });
33426
+ } catch {
33427
+ return [];
33428
+ }
33429
+ const out = [];
33430
+ for (const entry of entries) {
33431
+ const full = path26.join(dir, entry.name);
33432
+ if (entry.isDirectory()) {
33433
+ if (entry.name === "node_modules" || entry.name === "dist") continue;
33434
+ out.push(...await walkFiles(full));
33435
+ } else if (SOURCE_EXT.test(entry.name)) {
33436
+ out.push(full);
33437
+ }
33438
+ }
33439
+ return out;
33440
+ }
33441
+ async function resolveSpecifier(specifier, fromFile, root) {
33442
+ const bare = specifier.split("?")[0] ?? "";
33443
+ if (bare.includes("*")) return null;
33444
+ let base;
33445
+ if (bare.startsWith("/")) base = path26.join(root, bare.slice(1));
33446
+ else if (bare.startsWith(".")) base = path26.resolve(path26.dirname(fromFile), bare);
33447
+ else return null;
33448
+ for (const suffix of RESOLVE_CANDIDATES) {
33449
+ const candidate = base + suffix;
33450
+ if (SOURCE_EXT.test(candidate) && await isFile(candidate)) return candidate;
33451
+ }
33452
+ return null;
33453
+ }
33454
+ async function importedFiles(text2, file, root) {
33455
+ const out = [];
33456
+ for (const re2 of [IMPORT_RE, DYNAMIC_IMPORT_RE]) {
33457
+ for (const match of text2.matchAll(re2)) {
33458
+ const spec = match[1];
33459
+ if (spec === void 0) continue;
33460
+ const target = await resolveSpecifier(spec, file, root);
33461
+ if (target !== null) out.push(target);
33462
+ }
33463
+ }
33464
+ return out;
33465
+ }
33466
+ async function landingGraphFiles(root, slug) {
33467
+ const seen = /* @__PURE__ */ new Set();
33468
+ const queue = await walkFiles(path26.join(root, "src/pages", slug));
33469
+ while (queue.length > 0) {
33470
+ const file = queue.pop();
33471
+ if (file === void 0 || seen.has(file)) continue;
33472
+ let text2;
33473
+ try {
33474
+ text2 = await readFile20(file, "utf8");
33475
+ } catch {
33476
+ continue;
33477
+ }
33478
+ seen.add(file);
33479
+ for (const target of await importedFiles(text2, file, root)) {
33480
+ if (!seen.has(target)) queue.push(target);
33481
+ }
33482
+ }
33483
+ return new Set([...seen].map((abs) => path26.relative(root, abs).split(path26.sep).join("/")));
33484
+ }
33485
+
33486
+ // src/commands/experiment/composition.ts
33487
+ function sectionsUnder(files, slug) {
33488
+ const prefix = `src/pages/${slug}/`;
33489
+ return files.filter((file) => file.startsWith(prefix) && !file.slice(prefix.length).startsWith("index.")).sort();
33490
+ }
33491
+ function compositionOf(controlSlug, variantSlug, variantGraph) {
33492
+ return {
33493
+ forked: sectionsUnder(variantGraph, variantSlug),
33494
+ shared: sectionsUnder(variantGraph, controlSlug)
33495
+ };
33496
+ }
33497
+ async function readComposition(root, controlSlug, variantSlug) {
33498
+ try {
33499
+ const graph = await landingGraphFiles(root, variantSlug);
33500
+ return compositionOf(controlSlug, variantSlug, [...graph]);
33501
+ } catch {
33502
+ return void 0;
33503
+ }
33504
+ }
33505
+
33506
+ // src/commands/experiment/goal.ts
33507
+ function parseEventGoal(argument) {
33508
+ const [name, filter] = argument.split("/");
33509
+ if (!name) return { error: "Name the event: --goal event:request_demo" };
33510
+ if (!filter) return { kind: "custom_event", name };
33511
+ const separator = filter.indexOf("=");
33512
+ if (separator === -1) {
33513
+ return { error: `Write the property as key=value: --goal event:${name}/section=pricing` };
33514
+ }
33515
+ return { kind: "custom_event", name, property: filter.slice(0, separator), value: filter.slice(separator + 1) };
33516
+ }
33517
+ function parseGoal(raw) {
33518
+ if (raw === void 0 || raw === "") return void 0;
33519
+ const separator = raw.indexOf(":");
33520
+ const kind = separator === -1 ? raw : raw.slice(0, separator);
33521
+ const argument = separator === -1 ? "" : raw.slice(separator + 1);
33522
+ if (kind === "leads") return argument ? { kind: "conversion", flowSlug: argument } : { kind: "conversion" };
33523
+ if (kind === "click") return argument ? { kind: "outbound_click", targetHost: argument } : { kind: "outbound_click" };
33524
+ if (kind === "event") return parseEventGoal(argument);
33525
+ return {
33526
+ error: `Unknown goal "${raw}". Use leads, leads:<form>, event:<name>, event:<name>/<key>=<value>, click, or click:<host>`
33527
+ };
33528
+ }
33529
+
33530
+ // src/commands/experiment/hints.ts
33531
+ var RUNNING_HINT = {
33532
+ keep_running: (row) => `${row.experimentId} cannot yet tell the two pages apart \u2014 do not read its numbers as a result, and do not end it hoping for one. ${describeWait(row)}`,
33533
+ winner: (row) => `${row.experimentId} has a winner. Run \`baker experiment finish --id ${row.experimentId}\` to send all the traffic to it. That only re-routes between two pages already published \u2014 it does not edit either.`,
33534
+ no_difference: (row) => `${row.experimentId} measured the two pages as alike \u2014 it collected enough traffic to see the change it was looking for, and there was none. Finish it (the page you have stays live) and test a bigger change \u2014 a headline, an offer, the shape of the page, not a button colour.`,
33535
+ stopped_early_harmful: (row) => `${row.experimentId} is doing damage. Finish it now \u2014 traffic goes back to the original page.`,
33536
+ // Unreachable in practice — only `finish --abandon` produces it, and that
33537
+ // leaves the test finished. The Record is exhaustive by type so a verdict
33538
+ // added upstream fails to compile here rather than printing nothing.
33539
+ abandoned: (row) => `${row.experimentId} was stopped before it concluded and found nothing.`,
33540
+ invalid: (row) => `${row.experimentId} cannot be read: ${row.invalidReason === "crossover" ? "too many visitors saw both versions" : "the traffic did not split evenly"}. Finish it and start again rather than reporting anything from it.`
33541
+ };
33542
+ function describeWait(row) {
33543
+ if (row.willNotConclude) {
33544
+ return "This page's traffic will not settle it at any point, so abandon it with `--abandon` and test a bigger change.";
33545
+ }
33546
+ if (row.earliestDecisionAt === null) {
33547
+ return "This page has produced no visitors yet, so there is nothing to project a finish date from.";
33548
+ }
33549
+ return `Earliest it could say anything: ${new Date(row.earliestDecisionAt).toISOString().slice(0, 10)}.`;
33550
+ }
33551
+ function buildStatusHints(experiments) {
33552
+ const hints = experiments.filter((row) => row.status === "running").map((row) => RUNNING_HINT[row.verdict](row));
33553
+ if (experiments.length === 0) {
33554
+ hints.push(
33555
+ "No tests yet. `baker experiment plan --landing <slug> --variant <slug>` says whether a page has enough traffic for one before you build the alternative."
33556
+ );
33557
+ }
33558
+ return hints;
33559
+ }
33560
+ function asLift(value) {
33561
+ return `+${Math.round(value * 100)}%`;
33562
+ }
33563
+ function windowHint(days, detectable) {
33564
+ if (days === void 0) return null;
33565
+ if (detectable === null || detectable === void 0) {
33566
+ return `In ${days} days this page would not gather enough visitors to settle anything, at any size of change. The constraint is the traffic, not the test.`;
33567
+ }
33568
+ return `In ${days} days this page could only settle a change of ${asLift(detectable)} or more. Design the alternative to be at least that different \u2014 a smaller change needs traffic these ${days} days will not deliver, and the test would end saying nothing.`;
33569
+ }
33570
+ function buildPlanHints(plan, days) {
33571
+ const window2 = windowHint(days, plan.detectableLiftInDays);
33572
+ if (!plan.canRun) {
33573
+ return [
33574
+ plan.reason ?? "This test cannot run on this page.",
33575
+ ...window2 ? [window2] : [],
33576
+ "A test that cannot conclude is worse than no test \u2014 it still produces a number, and somebody acts on it."
33577
+ ];
33578
+ }
33579
+ const hints = [
33580
+ `This test measures ${plan.goalLabel}. That is fixed when it starts and cannot be changed later.`,
33581
+ // Fires at the exact moment the hypothesis gets invented, whether or not
33582
+ // the agent read the family doc. `plan` says a question CAN be settled here
33583
+ // and says nothing about which question is worth asking — and a well-run
33584
+ // test of a bad idea costs three weeks and returns `no_difference`.
33585
+ "Now decide WHAT to change, from evidence rather than taste. These numbers say which page and which step; they cannot say why. A second opinion answers why \u2014 Microsoft Clarity for what visitors actually do on the page, `baker gsc` for what they searched, `baker ads <platform>` for what the ad promised, `baker ga4` for the client's own measurement. `baker capabilities` lists everything this client has in one call \u2014 the surfaces, and under `tools` everything else that is reachable. Message match \u2014 what the ad or the search promised against what the page opens with \u2014 is the cheapest hypothesis worth testing and is wrong surprisingly often.",
33586
+ // The base always works, so a missing integration is never a reason to
33587
+ // stop. Naming what it would have shown is worth more to the client than
33588
+ // either a silent gap or a run that waited.
33589
+ "If none of that is connected, this page's own drop-off is enough to test on \u2014 say which source you lacked and what it would have told you, then run the test anyway. A test never waits on an integration."
33590
+ ];
33591
+ if (plan.estimatedDays !== null && plan.estimatedDays > 60) {
33592
+ hints.push(
33593
+ `At this page's traffic it would take about ${plan.estimatedDays} days. Consider testing a bigger change, which needs less traffic to detect.`
33594
+ );
33595
+ }
33596
+ if (window2) hints.push(window2);
33597
+ return hints;
33598
+ }
33599
+
33600
+ // ../api/src/experiments/composition.ts
33601
+ import { z as z29 } from "zod";
33602
+ var experimentCompositionSchema = z29.object({
33603
+ /** Components the variant owns its own copy of — the thing under test. */
33604
+ forked: z29.array(z29.string().max(300)).max(50),
33605
+ /** Components both pages render from one file, so an edit reaches both arms. */
33606
+ shared: z29.array(z29.string().max(300)).max(200)
33607
+ });
33608
+
33609
+ // ../api/src/experiments/goal.ts
33610
+ import { z as z30 } from "zod";
33611
+ var eventNameSchema = z30.string().min(1).max(120);
33612
+ var experimentGoalSchema = z30.discriminatedUnion("kind", [
33613
+ z30.object({
33614
+ kind: z30.literal("conversion"),
33615
+ /**
33616
+ * Narrow to one Form, by the slug that is its identity.
33617
+ *
33618
+ * Absent means every marked conversion on the page counts, which is the
33619
+ * right default for a landing with one Form and the wrong one for a page
33620
+ * carrying both a newsletter signup and a demo request.
33621
+ */
33622
+ flowSlug: z30.string().max(120).optional()
33623
+ }),
33624
+ z30.object({
33625
+ kind: z30.literal("custom_event"),
33626
+ name: eventNameSchema,
33627
+ /**
33628
+ * Narrow to one value of one of the event's own properties.
33629
+ *
33630
+ * `section_view` on its own is every section; `section_view` where
33631
+ * `section = pricing` is the one that matters. Without this a page would
33632
+ * have to encode the answer into the event name to be testable against it,
33633
+ * which is precisely what the properties map exists to stop.
33634
+ */
33635
+ property: z30.string().max(64).optional(),
33636
+ value: z30.string().max(255).optional()
33637
+ }),
33638
+ z30.object({
33639
+ kind: z30.literal("outbound_click"),
33640
+ /**
33641
+ * The destination host, as the row records it. Absent means any exit.
33642
+ *
33643
+ * A host rather than a full URL because that is what `event_name` carries
33644
+ * on an `outbound_click` row — `target_url` has the rest, and matching on
33645
+ * it would make the goal depend on query parameters a campaign rewrites.
33646
+ */
33647
+ targetHost: z30.string().max(255).optional()
33648
+ })
33649
+ ]);
33650
+
33651
+ // ../api/src/experiments/hypothesis.ts
33652
+ import { z as z31 } from "zod";
33653
+ var experimentEvidenceSourceSchema = z31.enum([
33654
+ "baker_analytics",
33655
+ "clarity",
33656
+ "ads",
33657
+ "search_console",
33658
+ "research",
33659
+ "inspiration",
33660
+ "client",
33661
+ "hunch"
33662
+ ]);
33663
+ var experimentEvidenceSchema = z31.object({
33664
+ source: experimentEvidenceSourceSchema,
33665
+ note: z31.string().min(1).max(200),
33666
+ /**
33667
+ * Where to go and look. Not a `z.url()` — a report inside Baker is reached by
33668
+ * a relative path, and rejecting those would push every in-product citation
33669
+ * into the note where nothing can link it.
33670
+ */
33671
+ href: z31.string().max(500).optional()
33672
+ });
33673
+ var experimentHypothesisSchema = z31.object({
33674
+ /**
33675
+ * The observation. The one genuinely new field, and the reason for the rest.
33676
+ *
33677
+ * Without it a lost test teaches nothing, because there is no belief for the
33678
+ * result to be evidence against — which is how a testing programme ends up as
33679
+ * a list of tests instead of a list of things learned.
33680
+ */
33681
+ because: z31.string().min(1).max(300),
33682
+ /**
33683
+ * The change, in the client's words.
33684
+ *
33685
+ * The machine-checkable truth lives in `plan.composition.forked`; this is what
33686
+ * a person reads. The two are allowed to differ in wording and cannot differ
33687
+ * in fact, because the brief shows both.
33688
+ */
33689
+ change: z31.string().min(1).max(200),
33690
+ /** Which way the goal is expected to move. See the note at the top of this file. */
33691
+ expect: z31.enum(["increase", "decrease"]).default("increase"),
33692
+ evidence: z31.array(experimentEvidenceSchema).max(5).optional()
33693
+ });
33694
+ var experimentHypothesisOutcomeSchema = z31.enum([
33695
+ "supported",
33696
+ "refuted_opposite",
33697
+ "refuted_no_effect",
33698
+ "unanswered"
33699
+ ]);
33700
+
33701
+ // ../api/src/experiments/wire.ts
33702
+ import { z as z32 } from "zod";
33703
+ var slugSchema = z32.string().min(1).max(120).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "Use a page slug, like `oferta`");
33704
+ var experimentPlanRequestSchema = z32.object({
33705
+ landingSlug: slugSchema,
33706
+ variantSlug: slugSchema,
33707
+ goal: experimentGoalSchema.optional(),
33708
+ /**
33709
+ * The smallest lift worth detecting, relative to the baseline. `0.2` is +20%.
33710
+ *
33711
+ * The single most consequential number a caller supplies, and the one it is
33712
+ * most tempting to set small. A test sized for +5% on a 3% page needs a
33713
+ * quarter of a million visitors per arm; `plan` will say so rather than
33714
+ * running it.
33715
+ */
33716
+ minDetectableRelativeLift: z32.number().gt(0).max(10).optional(),
33717
+ /**
33718
+ * "I have this many days — what could I even see?"
33719
+ *
33720
+ * Read-only, and deliberately not a setting. It does NOT shorten the test:
33721
+ * the finish line is a number of visitors fixed before the test starts, and a
33722
+ * horizon that can be moved is not a horizon. What it does is answer the
33723
+ * question a refusal leaves hanging — `plan` says a +20% lift needs three
33724
+ * weeks this page will not deliver, and this says what those weeks WOULD
33725
+ * settle, which is a change somebody can actually go and make.
33726
+ */
33727
+ days: z32.number().int().gt(0).max(365).optional()
33728
+ });
33729
+ var experimentStartRequestSchema = experimentPlanRequestSchema.extend({
33730
+ hypothesis: experimentHypothesisSchema,
33731
+ /**
33732
+ * What the two arms are made of, read off the import graph by the caller at
33733
+ * the moment the test starts.
33734
+ *
33735
+ * Optional on the wire because the backend cannot compute it — only the
33736
+ * Company Workspace has the files — and a workspace that cannot answer must
33737
+ * not be blocked from running a test. The brief degrades to naming the two
33738
+ * pages instead of naming the section under test.
33739
+ */
33740
+ composition: experimentCompositionSchema.optional()
33741
+ });
33742
+ var experimentFinishRequestSchema = z32.object({
33743
+ experimentId: z32.string().min(1).max(64),
33744
+ /**
33745
+ * Stop the test, keep the page that is live, and record no result.
33746
+ *
33747
+ * The only way a person or an agent can end a test that is still
33748
+ * `keep_running`, and it is deliberately its own field rather than a
33749
+ * `survivor` they get to pick: an abandoned test has no winner, keeps the
33750
+ * control, and is recorded as `abandoned` so it can never be reported later
33751
+ * as a result. Without the separate flag, "stop this" and "the variant won"
33752
+ * would be the same call.
33753
+ *
33754
+ * **It overrides the verdict, and does not defer to it.** The only outcome it
33755
+ * can actually disagree with is a variant win — every other one keeps the
33756
+ * control anyway — so reading it as "apply only when the numbers have nothing
33757
+ * to say" meant that abandoning a won test handed all the traffic to the
33758
+ * variant instead of keeping the original: the opposite of what this field
33759
+ * promises, on live visitors, from a flag whose whole purpose is to be the
33760
+ * conservative choice. The response reports `discardedVerdict` when it did
33761
+ * override one, because throwing away a real finding should be said out loud.
33762
+ */
33763
+ abandon: z32.boolean().optional()
33764
+ });
33765
+ var experimentStatusRequestSchema = z32.object({
33766
+ experimentId: z32.string().max(64).optional(),
33767
+ full: z32.boolean().optional()
33768
+ });
33769
+ var experimentPlanResponseSchema = z32.object({
33770
+ canRun: z32.boolean(),
33771
+ /** Present when `canRun` is false. Product language, ready to show. */
33772
+ reason: z32.string().optional(),
33773
+ baselineRate: z32.number(),
33774
+ minDetectableRelativeLift: z32.number(),
33775
+ /** `null` when no amount of traffic could settle the hypothesis. */
33776
+ requiredPerVariant: z32.number().nullable(),
33777
+ /** Visitors a day the page has been getting, per arm once split. */
33778
+ dailyVisitorsPerVariant: z32.number(),
33779
+ /** `null` when the page has no traffic to project from. */
33780
+ estimatedDays: z32.number().nullable(),
33781
+ goal: experimentGoalSchema,
33782
+ goalLabel: z32.string(),
33783
+ /**
33784
+ * The smallest lift `days` of this page's traffic could detect, when `days`
33785
+ * was asked for. `null` when it was not, or when no lift at all would be
33786
+ * detectable in that window.
33787
+ */
33788
+ detectableLiftInDays: z32.number().nullable().optional()
33789
+ });
33790
+ var experimentVerdictSchema = z32.enum([
33791
+ "keep_running",
33792
+ "winner",
33793
+ "no_difference",
33794
+ "invalid",
33795
+ "stopped_early_harmful",
33796
+ /**
33797
+ * Stopped by a person before it concluded. Never produced by the stopping
33798
+ * rule — only by `finish --abandon`.
33799
+ *
33800
+ * Its own verdict rather than folded into `no_difference`, because they are
33801
+ * opposite claims: one says the two versions were measured and found alike,
33802
+ * the other says nobody ever found out. Collapsing them would manufacture the
33803
+ * exact finding this feature exists to refuse.
33804
+ */
33805
+ "abandoned"
33806
+ ]);
33807
+ var experimentArmSchema = z32.object({
33808
+ slug: z32.string(),
33809
+ visitors: z32.number(),
33810
+ conversions: z32.number(),
33811
+ rate: z32.number()
33812
+ });
33813
+ var experimentStatusRowSchema = z32.object({
33814
+ experimentId: z32.string(),
33815
+ landingSlug: z32.string(),
33816
+ variantSlug: z32.string(),
33817
+ hypothesis: experimentHypothesisSchema,
33818
+ /**
33819
+ * The hypothesis as one sentence, built from its parts plus the goal and the
33820
+ * lift on this same row. Rendered backend-side so what a client reads in chat
33821
+ * and what they read on the dashboard can never be two different sentences.
33822
+ */
33823
+ hypothesisStatement: z32.string(),
33824
+ /**
33825
+ * What the test found out about the belief — derived from `verdict` and
33826
+ * `winner`, never entered.
33827
+ *
33828
+ * Its own field because the verdict and the outcome come apart in exactly the
33829
+ * case worth catching: a `winner` the *control* won is not a win, it is the
33830
+ * change having made things worse.
33831
+ */
33832
+ hypothesisOutcome: experimentHypothesisOutcomeSchema,
33833
+ /** What the two arms differ by. Absent for a test started before this was recorded. */
33834
+ composition: experimentCompositionSchema.optional(),
33835
+ /**
33836
+ * The finish line this test was sized for. On the row rather than only inside
33837
+ * the plan because the claim is not readable without it — "leads should go up"
33838
+ * is not a hypothesis, "leads should go up by at least 20%" is.
33839
+ */
33840
+ minDetectableRelativeLift: z32.number(),
33841
+ goalLabel: z32.string(),
33842
+ status: z32.enum(["running", "finished"]),
33843
+ startedAt: z32.number(),
33844
+ finishedAt: z32.number().nullable(),
33845
+ verdict: experimentVerdictSchema,
33846
+ /** Set only on `winner`. Which page to keep. */
33847
+ winner: z32.enum(["control", "variant"]).nullable(),
33848
+ /** Set only on `invalid`. Why the numbers cannot be read. */
33849
+ invalidReason: z32.enum(["sample_ratio_mismatch", "crossover"]).nullable(),
33850
+ /** What a person should be told, in one sentence, in product language. */
33851
+ summary: z32.string(),
33852
+ control: experimentArmSchema,
33853
+ variant: experimentArmSchema,
33854
+ /**
33855
+ * How close this test is to being able to say anything at all, 0 to 1.
33856
+ *
33857
+ * Not progress toward a planned sample — there is no longer one to progress
33858
+ * toward. It is the traffic behind the test as a share of the traffic its own
33859
+ * measured variance says it needs, so it moves when the page gets noisier or
33860
+ * steadier rather than only when visitors arrive.
33861
+ */
33862
+ precision: z32.number(),
33863
+ /** When the test could first say something. `null` with no traffic to project from. */
33864
+ earliestDecisionAt: z32.number().nullable(),
33865
+ /**
33866
+ * True when no amount of this page's traffic would settle the comparison.
33867
+ * A different thing from a page that is merely slow, and the reason to
33868
+ * abandon a test rather than leave it running.
33869
+ */
33870
+ willNotConclude: z32.boolean(),
33871
+ /** Printing only. Never a reason to act — `verdict` is. */
33872
+ evidence: z32.object({
33873
+ relativeLift: z32.number(),
33874
+ probabilityVariantBetter: z32.number(),
33875
+ controlInterval: z32.tuple([z32.number(), z32.number()]),
33876
+ variantInterval: z32.tuple([z32.number(), z32.number()]),
33877
+ /** The anytime-valid interval on the relative lift — what the verdict is read from. */
33878
+ liftInterval: z32.tuple([z32.number(), z32.number()])
33879
+ }).optional()
33880
+ });
33881
+ var experimentStatusResponseSchema = z32.object({
33882
+ experiments: z32.array(experimentStatusRowSchema)
33883
+ });
33884
+
33885
+ // src/commands/experiment/hypothesis.ts
33886
+ var SOURCES = experimentEvidenceSourceSchema.options.join(", ");
33887
+ function parseOne(raw) {
33888
+ const separator = raw.indexOf(":");
33889
+ if (separator === -1) {
33890
+ return { error: `Write the evidence as source:note \u2014 for example \`${raw}:what you saw\`. Sources: ${SOURCES}` };
33891
+ }
33892
+ const source = experimentEvidenceSourceSchema.safeParse(raw.slice(0, separator).trim());
33893
+ if (!source.success) {
33894
+ return { error: `\u201C${raw.slice(0, separator).trim()}\u201D is not one of the evidence sources. Use one of: ${SOURCES}` };
33895
+ }
33896
+ const rest = raw.slice(separator + 1).trim();
33897
+ const pipe = rest.lastIndexOf("|");
33898
+ const note = (pipe === -1 ? rest : rest.slice(0, pipe)).trim();
33899
+ const href = pipe === -1 ? void 0 : rest.slice(pipe + 1).trim();
33900
+ if (note === "") {
33901
+ return { error: `Say what ${source.data} showed: --evidence "${source.data}:what you saw"` };
33902
+ }
33903
+ return href ? { source: source.data, note, href } : { source: source.data, note };
33904
+ }
33905
+ function parseEvidence(raw) {
33906
+ if (raw === void 0) return void 0;
33907
+ const entries = (Array.isArray(raw) ? raw : [raw]).filter((entry) => entry !== "");
33908
+ if (entries.length === 0) return void 0;
33909
+ const parsed = [];
33910
+ for (const entry of entries) {
33911
+ const one = parseOne(entry);
33912
+ if ("error" in one) return one;
33913
+ parsed.push(one);
33914
+ }
33915
+ return parsed;
33916
+ }
33917
+
33918
+ // src/commands/experiment/index.ts
33919
+ var GOAL_ARG = {
33920
+ type: "string",
33921
+ description: "What counts as success, and it is fixed for the life of the test. `leads` (default \u2014 whatever this company already marks as a conversion), `leads:<form>`, `event:<name>` for an event the page declares itself (see `baker analytics events`), `event:<name>/<key>=<value>` to narrow it, `click` or `click:<host>` for leaving the site. Pick the thing the business actually wants; a test that measures the wrong outcome can have the variant win and the revenue fall.",
33922
+ required: false
33923
+ };
33924
+ var LIFT_ARG = {
33925
+ type: "string",
33926
+ description: "The smallest improvement worth detecting, relative \u2014 0.2 is +20% (default). The single most consequential number here: halving it roughly quadruples the traffic needed. Ask for the smallest lift that would actually change what the client does, not the smallest one you would like to see.",
33927
+ required: false
33928
+ };
33929
+ var DAYS_ARG = {
33930
+ type: "string",
33931
+ description: "How many days you have, if that is the constraint. This does NOT shorten the test \u2014 the finish line is a visitor count fixed before it starts \u2014 it answers the other question: what a window that long could settle on this page's traffic. Use it when `plan` refuses and you need to know what WOULD fit.",
33932
+ required: false
33933
+ };
33934
+ var BECAUSE_ARG = {
33935
+ type: "string",
33936
+ description: "What you SAW that makes this worth testing \u2014 the observation, not the change. \u201C60% of visitors never scroll past the hero\u201D, \u201CCPL doubled after the new headline\u201D. This is the field that makes a losing test worth something: without it there is no belief for the result to be evidence against, and the next test is no better informed than this one.",
33937
+ required: true
33938
+ };
33939
+ var CHANGE_ARG = {
33940
+ type: "string",
33941
+ description: "What is different about the alternative version, in the client's words \u2014 \u201Cput the booking form in the hero\u201D. One thing. Say it as the reader of the report would.",
33942
+ required: true
33943
+ };
33944
+ var EXPECT_ARG = {
33945
+ type: "string",
33946
+ description: "Which way you expect the goal to move: `increase` (default) or `decrease`. This is what lets Baker report whether your hypothesis HELD, rather than only which page won \u2014 a test the original page wins is not a win, it is the change having made things worse, and that is the most useful thing a test can tell you.",
33947
+ required: false
33948
+ };
33949
+ var EVIDENCE_ARG = {
33950
+ type: "string",
33951
+ description: "Where the observation came from, as `source:what it showed`, optionally `|link`. Repeatable. Sources: baker_analytics, clarity, ads, search_console, research, inspiration, client, hunch. Use `hunch` honestly when there is no data \u2014 a stated guess is worth more than a guess dressed as analytics.",
33952
+ required: false
33953
+ };
33954
+ function fail5(message) {
33955
+ writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
33956
+ process.exit(1);
33957
+ }
33958
+ function reportApiError(error) {
33959
+ if (error instanceof ApiError) {
33960
+ writeJsonEnvelope({ ok: false, error: { code: error.code ?? "ERROR", message: error.message } });
33961
+ process.exit(1);
33962
+ }
33963
+ throw error;
33964
+ }
33965
+ registerSchema({
33966
+ command: "experiment.plan",
33967
+ description: "Whether a page has enough traffic to settle a question, before anything is built",
33968
+ args: {
33969
+ landing: { type: "string", description: "The page under test, by slug", required: true },
33970
+ variant: { type: "string", description: "The alternative page's slug", required: true },
33971
+ goal: GOAL_ARG,
33972
+ lift: LIFT_ARG,
33973
+ days: DAYS_ARG
33974
+ }
33975
+ });
33976
+ var planCommand = defineCommand112({
33977
+ meta: {
33978
+ name: "plan",
33979
+ description: "Start here, BEFORE building the alternative page. Says whether this page gets enough traffic to settle the question, how long it would take, and whether the thing you want to measure has ever happened on it. Most landing pages cannot run most tests \u2014 finding that out now costs one call, and finding it out later costs three weeks and a decision made on noise."
33980
+ },
33981
+ args: {
33982
+ landing: { type: "string", description: "The page under test, by slug", required: true },
33983
+ variant: { type: "string", description: "The alternative page's slug", required: true },
33984
+ goal: GOAL_ARG,
33985
+ lift: LIFT_ARG,
33986
+ days: DAYS_ARG
33987
+ },
33988
+ run: async ({ args }) => {
33989
+ const goal = parseGoal(args.goal ? String(args.goal) : void 0);
33990
+ if (goal && "error" in goal) fail5(goal.error);
33991
+ try {
33992
+ const response = await apiPost("/api/experiments/plan", {
33993
+ landingSlug: String(args.landing),
33994
+ variantSlug: String(args.variant),
33995
+ ...goal ? { goal } : {},
33996
+ ...args.lift ? { minDetectableRelativeLift: Number(args.lift) } : {},
33997
+ ...args.days ? { days: Number(args.days) } : {}
33998
+ });
33999
+ writeJsonEnvelope({
34000
+ ok: true,
34001
+ data: response.data,
34002
+ hints: buildPlanHints(response.data, args.days ? Number(args.days) : void 0)
34003
+ });
34004
+ } catch (error) {
34005
+ reportApiError(error);
34006
+ }
34007
+ }
34008
+ });
34009
+ registerSchema({
34010
+ command: "experiment.start",
34011
+ description: "Stage an A/B test between a page and its alternative",
34012
+ args: {
34013
+ landing: { type: "string", description: "The page under test, by slug", required: true },
34014
+ variant: { type: "string", description: "The alternative page's slug", required: true },
34015
+ because: BECAUSE_ARG,
34016
+ change: CHANGE_ARG,
34017
+ expect: EXPECT_ARG,
34018
+ evidence: EVIDENCE_ARG,
34019
+ goal: GOAL_ARG,
34020
+ lift: LIFT_ARG
34021
+ }
34022
+ });
34023
+ var startCommand = defineCommand112({
34024
+ meta: {
34025
+ name: "start",
34026
+ description: "Stage a test between the page and its alternative. Both are ordinary published pages; visitors are split 50/50 at the edge, so there is no flicker and nothing for an ad blocker to suppress. Nothing goes live until the session is published \u2014 the person publishing is the review of the alternative page. Re-runs `plan` first and refuses on the same grounds."
34027
+ },
34028
+ args: {
34029
+ landing: { type: "string", description: "The page under test, by slug", required: true },
34030
+ variant: { type: "string", description: "The alternative page's slug", required: true },
34031
+ because: BECAUSE_ARG,
34032
+ change: CHANGE_ARG,
34033
+ expect: EXPECT_ARG,
34034
+ evidence: EVIDENCE_ARG,
34035
+ goal: GOAL_ARG,
34036
+ lift: LIFT_ARG
34037
+ },
34038
+ run: async ({ args }) => {
34039
+ const goal = parseGoal(args.goal ? String(args.goal) : void 0);
34040
+ if (goal && "error" in goal) fail5(goal.error);
34041
+ const expect = args.expect === void 0 ? "increase" : String(args.expect);
34042
+ if (expect !== "increase" && expect !== "decrease") {
34043
+ fail5("`--expect` is either `increase` or `decrease` \u2014 which way should the goal move if you are right?");
34044
+ }
34045
+ const evidence = parseEvidence(args.evidence);
34046
+ if (evidence && "error" in evidence) fail5(evidence.error);
34047
+ const composition = await readComposition(process.cwd(), String(args.landing), String(args.variant));
34048
+ try {
34049
+ const response = await apiPost("/api/experiments/start", {
34050
+ landingSlug: String(args.landing),
34051
+ variantSlug: String(args.variant),
34052
+ hypothesis: {
34053
+ because: String(args.because),
34054
+ change: String(args.change),
34055
+ expect,
34056
+ ...evidence ? { evidence } : {}
34057
+ },
34058
+ ...composition ? { composition } : {},
34059
+ ...goal ? { goal } : {},
34060
+ ...args.lift ? { minDetectableRelativeLift: Number(args.lift) } : {}
34061
+ });
34062
+ writeJsonEnvelope({
34063
+ ok: true,
34064
+ data: response.data,
34065
+ hints: [
34066
+ // The one composition worth interrupting for: `baker landing variant`
34067
+ // with no `--fork` succeeds and produces a page that renders exactly
34068
+ // the control, so the test would run for weeks against itself.
34069
+ ...composition && composition.forked.length === 0 ? [
34070
+ `\u201C${String(args.variant)}\u201D does not have its own copy of any section \u2014 it renders exactly the same page as \u201C${String(args.landing)}\u201D, so this test cannot find anything. Fork the section you want to test (\`baker landing variant ${String(args.landing)} ${String(args.variant)} --fork Hero.astro\`) and edit only that file.`
34071
+ ] : [],
34072
+ "Staged. The split starts when this session is published \u2014 publishing is the review of the alternative page.",
34073
+ "Check it as often as you like \u2014 `baker experiment status` is safe to read at any moment and holds its error rate however often you ask. What is never safe is reading the numbers instead of the verdict: `keep_running` means this test cannot yet tell the two pages apart, whatever the rates happen to say today."
34074
+ ]
34075
+ });
34076
+ } catch (error) {
34077
+ reportApiError(error);
34078
+ }
34079
+ }
34080
+ });
34081
+ registerSchema({
34082
+ command: "experiment.status",
34083
+ description: "The verdict on every A/B test, running and finished",
34084
+ args: {
34085
+ id: { type: "string", description: "One test, by its id", required: false },
34086
+ full: { type: "boolean", description: "Include the posteriors behind the verdict", required: false }
34087
+ }
34088
+ });
34089
+ var statusCommand4 = defineCommand112({
34090
+ meta: {
34091
+ name: "status",
34092
+ description: "The verdict on each test. Read `verdict` and nothing else to decide: keep_running | winner | no_difference | invalid | stopped_early_harmful. `summary` is one sentence you can show the client. The numbers under `--full` are for printing, never for deciding \u2014 a test that says keep_running has not finished, however good its numbers look."
34093
+ },
34094
+ args: {
34095
+ id: { type: "string", description: "One test, by its id", required: false },
34096
+ full: { type: "boolean", description: "Include the posteriors behind the verdict", required: false }
34097
+ },
34098
+ run: async ({ args }) => {
34099
+ try {
34100
+ const response = await apiPost("/api/experiments/status", {
34101
+ ...args.id ? { experimentId: String(args.id) } : {},
34102
+ ...args.full === true ? { full: true } : {}
34103
+ });
34104
+ writeJsonEnvelope({
34105
+ ok: true,
34106
+ data: response.data,
34107
+ hints: buildStatusHints(response.data.experiments)
34108
+ });
34109
+ } catch (error) {
34110
+ reportApiError(error);
34111
+ }
34112
+ }
34113
+ });
34114
+ registerSchema({
34115
+ command: "experiment.finish",
34116
+ description: "End a test and send all its traffic to the surviving page",
34117
+ args: {
34118
+ id: { type: "string", description: "The test, by its id", required: true },
34119
+ abandon: {
34120
+ type: "boolean",
34121
+ description: "Stop the test, keep the original page, and record no result. The only way to end a test that has not concluded \u2014 and it OVERRIDES a verdict if there is one, throwing that finding away. Do not pass it by reflex.",
34122
+ required: false
34123
+ }
34124
+ }
34125
+ });
34126
+ var finishCommand = defineCommand112({
34127
+ meta: {
34128
+ name: "finish",
34129
+ description: "End a test and send all its traffic to whichever page survived. Only a test whose verdict has concluded can be finished with a result \u2014 a `keep_running` test can only be `--abandon`ed, which keeps the original page and records no finding. Promotion is a traffic change over two pages that were both already published and reviewed; it does not edit either page."
34130
+ },
34131
+ args: {
34132
+ id: { type: "string", description: "The test, by its id", required: true },
34133
+ abandon: {
34134
+ type: "boolean",
34135
+ description: "Stop the test, keep the original page, and record no result. The only way to end a test that has not concluded \u2014 and it OVERRIDES a verdict if there is one, throwing that finding away. Do not pass it by reflex.",
34136
+ required: false
34137
+ }
34138
+ },
34139
+ run: async ({ args }) => {
34140
+ try {
34141
+ const response = await apiPost("/api/experiments/finish", {
34142
+ experimentId: String(args.id),
34143
+ ...args.abandon === true ? { abandon: true } : {}
34144
+ });
34145
+ writeJsonEnvelope({
34146
+ ok: true,
34147
+ data: response.data,
34148
+ hints: [
34149
+ `Staged. All the traffic goes to the ${response.data.survivor === "variant" ? "alternative" : "original"} page when this session is published.`,
34150
+ ...response.data.discardedVerdict ? [
34151
+ `You abandoned a test that HAD concluded (${response.data.discardedVerdict}). The original page keeps the traffic and no result is recorded \u2014 the finding is gone. If that was not what you meant, run this again without --abandon.`
34152
+ ] : [],
34153
+ "If the alternative won and you want it to become the page itself, that is an ordinary page edit afterwards \u2014 copy its content over and delete the alternative."
34154
+ ]
34155
+ });
34156
+ } catch (error) {
34157
+ reportApiError(error);
34158
+ }
34159
+ }
34160
+ });
34161
+ var experimentCommand = defineCommand112({
34162
+ meta: {
34163
+ name: "experiment",
34164
+ description: "Run an A/B test between a landing page and an alternative version of it, and be told which one is better. Always `plan` first: most pages do not get enough traffic to settle most questions, and a test that cannot conclude still produces a number somebody will act on."
34165
+ },
34166
+ subCommands: {
34167
+ plan: planCommand,
34168
+ start: startCommand,
34169
+ status: statusCommand4,
34170
+ finish: finishCommand
34171
+ }
34172
+ });
34173
+
33383
34174
  // src/commands/flows/index.ts
33384
- import { defineCommand as defineCommand116 } from "citty";
34175
+ import { defineCommand as defineCommand117 } from "citty";
33385
34176
 
33386
34177
  // src/commands/flows/add.ts
33387
34178
  import { randomUUID } from "crypto";
33388
- import { defineCommand as defineCommand112 } from "citty";
34179
+ import { defineCommand as defineCommand113 } from "citty";
33389
34180
 
33390
34181
  // src/commands/flows/shared.ts
33391
34182
  import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync9, writeFileSync as writeFileSync3 } from "fs";
@@ -33438,12 +34229,12 @@ function collectSideEffects(tree) {
33438
34229
  );
33439
34230
  }
33440
34231
  function readFlowTree(slug) {
33441
- const path40 = join3(flowsDir(), slug, "_data.json");
33442
- if (!existsSync5(path40)) {
34232
+ const path43 = join3(flowsDir(), slug, "_data.json");
34233
+ if (!existsSync5(path43)) {
33443
34234
  failLocal(`No form "${slug}". Run "baker flows list" to see the forms in this workspace.`);
33444
34235
  }
33445
34236
  try {
33446
- return JSON.parse(readFileSync9(path40, "utf-8"));
34237
+ return JSON.parse(readFileSync9(path43, "utf-8"));
33447
34238
  } catch (error) {
33448
34239
  failLocal(`Could not read form "${slug}": ${error instanceof Error ? error.message : String(error)}`);
33449
34240
  }
@@ -33595,7 +34386,7 @@ registerSchema({
33595
34386
  parent: { type: "string", description: "Parent node id. Omit to append to the root's children.", required: false }
33596
34387
  }
33597
34388
  });
33598
- var addNodeCommand = defineCommand112({
34389
+ var addNodeCommand = defineCommand113({
33599
34390
  meta: {
33600
34391
  name: "add-node",
33601
34392
  description: 'Add a step to a form, with the skeleton its type requires. Example: baker flows add-node contact --type customForm --name "Contact Info"'
@@ -33691,7 +34482,7 @@ registerSchema({
33691
34482
  }
33692
34483
  }
33693
34484
  });
33694
- var addSideEffectCommand = defineCommand112({
34485
+ var addSideEffectCommand = defineCommand113({
33695
34486
  meta: {
33696
34487
  name: "add-side-effect",
33697
34488
  description: "Add a side effect to a step, with the skeleton its type requires. Example: baker flows add-side-effect contact --node <id> --type googleSpreadsheet"
@@ -33773,7 +34564,7 @@ var addSideEffectCommand = defineCommand112({
33773
34564
 
33774
34565
  // src/commands/flows/map.ts
33775
34566
  import { readFileSync as readFileSync11 } from "fs";
33776
- import { defineCommand as defineCommand113 } from "citty";
34567
+ import { defineCommand as defineCommand114 } from "citty";
33777
34568
 
33778
34569
  // src/commands/flows/value-expression.ts
33779
34570
  import { existsSync as existsSync6, readFileSync as readFileSync10 } from "fs";
@@ -33835,10 +34626,10 @@ function parseValueExpression(raw) {
33835
34626
  return parts.map(parsePart);
33836
34627
  }
33837
34628
  function trackingFieldIds() {
33838
- const path40 = join4(flowsDir(), "..", "tracking.ts");
33839
- if (!existsSync6(path40)) return null;
34629
+ const path43 = join4(flowsDir(), "..", "tracking.ts");
34630
+ if (!existsSync6(path43)) return null;
33840
34631
  try {
33841
- const source = readFileSync10(path40, "utf-8");
34632
+ const source = readFileSync10(path43, "utf-8");
33842
34633
  const block2 = source.match(/TRACKING_FIELD_IDS\s*=\s*\[([\s\S]*?)\]\s*as const/)?.[1];
33843
34634
  if (!block2) return null;
33844
34635
  const ids = [...block2.matchAll(/"(tracking\.[a-z0-9_]+)"/g)].map((match) => match[1]);
@@ -34390,13 +35181,13 @@ function specsFromFile(parsed) {
34390
35181
  return `${destField}${type}=${entry?.value ?? ""}`;
34391
35182
  });
34392
35183
  }
34393
- function readSpecFile(path40) {
35184
+ function readSpecFile(path43) {
34394
35185
  let raw;
34395
35186
  try {
34396
- raw = path40 === "-" ? readFileSync11(0, "utf-8") : readFileSync11(path40, "utf-8");
35187
+ raw = path43 === "-" ? readFileSync11(0, "utf-8") : readFileSync11(path43, "utf-8");
34397
35188
  } catch (error) {
34398
35189
  refuse(
34399
- `Could not read ${path40 === "-" ? "the mapping from stdin" : `"${path40}"`}: ${error instanceof Error ? error.message : String(error)}`
35190
+ `Could not read ${path43 === "-" ? "the mapping from stdin" : `"${path43}"`}: ${error instanceof Error ? error.message : String(error)}`
34400
35191
  );
34401
35192
  }
34402
35193
  let parsed;
@@ -34404,7 +35195,7 @@ function readSpecFile(path40) {
34404
35195
  parsed = JSON.parse(raw);
34405
35196
  } catch (error) {
34406
35197
  refuse(
34407
- `${path40 === "-" ? "stdin" : `"${path40}"`} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
35198
+ `${path43 === "-" ? "stdin" : `"${path43}"`} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
34408
35199
  'Expected { "map": { "<destField>": "<value>", \u2026 } }'
34409
35200
  );
34410
35201
  }
@@ -34546,7 +35337,7 @@ async function mapOptions(args, fromFile, into) {
34546
35337
  destinationFields: hubspotForm ? await hubspotFieldCatalog(hubspotForm) : void 0
34547
35338
  };
34548
35339
  }
34549
- var mapCommand2 = defineCommand113({
35340
+ var mapCommand2 = defineCommand114({
34550
35341
  meta: {
34551
35342
  name: "map",
34552
35343
  description: "Map form fields onto any value a side effect sends \u2014 never hand-write a mapping. Start here: baker flows map contact --from map.json --write"
@@ -34644,7 +35435,7 @@ var mapCommand2 = defineCommand113({
34644
35435
  });
34645
35436
 
34646
35437
  // src/commands/flows/normalize.ts
34647
- import { defineCommand as defineCommand114 } from "citty";
35438
+ import { defineCommand as defineCommand115 } from "citty";
34648
35439
  var OPTIONAL_STRINGS = ["oauthProviderId"];
34649
35440
  var ARRAY_FIELDS = [
34650
35441
  "fieldMapping",
@@ -34658,18 +35449,18 @@ var ARRAY_FIELDS = [
34658
35449
  "tagIds"
34659
35450
  ];
34660
35451
  var ARRAY_OWNERS = ["", "body"];
34661
- function dropUnsetOptionals(sideEffect, path40) {
35452
+ function dropUnsetOptionals(sideEffect, path43) {
34662
35453
  return OPTIONAL_STRINGS.flatMap((key) => {
34663
35454
  if (!(key in sideEffect) || sideEffect[key] !== null && sideEffect[key] !== "") return [];
34664
35455
  delete sideEffect[key];
34665
- return [{ path: path40, change: `dropped \`${key}\` (an optional string is absent, never null)` }];
35456
+ return [{ path: path43, change: `dropped \`${key}\` (an optional string is absent, never null)` }];
34666
35457
  });
34667
35458
  }
34668
- function fillNulledArrays(target, prefix, path40) {
35459
+ function fillNulledArrays(target, prefix, path43) {
34669
35460
  return ARRAY_FIELDS.flatMap((key) => {
34670
35461
  if (!(key in target) || target[key] !== null) return [];
34671
35462
  target[key] = [];
34672
- return [{ path: path40, change: `\`${prefix}${key}: null\` \u2192 \`[]\`` }];
35463
+ return [{ path: path43, change: `\`${prefix}${key}: null\` \u2192 \`[]\`` }];
34673
35464
  });
34674
35465
  }
34675
35466
  function sideEffectsOf(node) {
@@ -34679,13 +35470,13 @@ function sideEffectsOf(node) {
34679
35470
  );
34680
35471
  }
34681
35472
  function normalizeSideEffect(sideEffect, where) {
34682
- const path40 = `${where} \u2192 ${String(sideEffect.id ?? "side effect")}`;
35473
+ const path43 = `${where} \u2192 ${String(sideEffect.id ?? "side effect")}`;
34683
35474
  const arrays = ARRAY_OWNERS.flatMap((owner) => {
34684
35475
  const target = owner ? sideEffect[owner] : sideEffect;
34685
35476
  if (!target || typeof target !== "object") return [];
34686
- return fillNulledArrays(target, owner ? `${owner}.` : "", path40);
35477
+ return fillNulledArrays(target, owner ? `${owner}.` : "", path43);
34687
35478
  });
34688
- return [...dropUnsetOptionals(sideEffect, path40), ...arrays];
35479
+ return [...dropUnsetOptionals(sideEffect, path43), ...arrays];
34689
35480
  }
34690
35481
  function normalizeFlowTree(tree) {
34691
35482
  const changes = [];
@@ -34713,7 +35504,7 @@ registerSchema({
34713
35504
  }
34714
35505
  }
34715
35506
  });
34716
- var normalizeCommand = defineCommand114({
35507
+ var normalizeCommand = defineCommand115({
34717
35508
  meta: {
34718
35509
  name: "normalize",
34719
35510
  description: "Repair the shapes a form file is allowed to get slightly wrong. Example: baker flows normalize contact --write"
@@ -34744,7 +35535,7 @@ var normalizeCommand = defineCommand114({
34744
35535
  });
34745
35536
 
34746
35537
  // src/commands/flows/schema.ts
34747
- import { defineCommand as defineCommand115 } from "citty";
35538
+ import { defineCommand as defineCommand116 } from "citty";
34748
35539
  registerSchema({
34749
35540
  command: "flows.schema",
34750
35541
  description: "The shape of one node or side-effect type: a pasteable skeleton, who fills each part, and the reference file with the craft. Start here before authoring anything in a form's _data.json.",
@@ -34769,7 +35560,7 @@ function triggerHints(triggers) {
34769
35560
  `A side effect on this step carries \`triggerId: "${triggers.completion}"\`, and \`baker flows add-side-effect\` writes it for you. The trigger belongs to the STEP, not to the side-effect type: one that does not match is valid, ships, and never runs.` + (others.length > 0 ? ` Other moments this step raises: ${others.join(", ")}.` : "")
34770
35561
  ];
34771
35562
  }
34772
- var schemaCommand = defineCommand115({
35563
+ var schemaCommand = defineCommand116({
34773
35564
  meta: {
34774
35565
  name: "schema",
34775
35566
  description: "Show a node or side-effect type's shape. Start here: baker flows schema node customForm \xB7 baker flows schema side-effect googleSpreadsheet \xB7 baker flows schema (lists both)"
@@ -34903,7 +35694,7 @@ function displayName(slug) {
34903
35694
  const name = tree.displayName;
34904
35695
  return typeof name === "string" && name.trim() ? name.trim() : slug;
34905
35696
  }
34906
- var listCommand12 = defineCommand116({
35697
+ var listCommand12 = defineCommand117({
34907
35698
  meta: {
34908
35699
  name: "list",
34909
35700
  description: "List Forms in this workspace with the count of confidential fields still needing setup. Example: baker flows list"
@@ -34924,7 +35715,7 @@ var listCommand12 = defineCommand116({
34924
35715
  });
34925
35716
  }
34926
35717
  });
34927
- var showCommand3 = defineCommand116({
35718
+ var showCommand3 = defineCommand117({
34928
35719
  meta: {
34929
35720
  name: "show",
34930
35721
  description: "Show a Form's confidential fields and their configuration status (never secret values). Example: baker flows show contact"
@@ -34952,7 +35743,7 @@ var showCommand3 = defineCommand116({
34952
35743
  });
34953
35744
  }
34954
35745
  });
34955
- var flowsCommand = defineCommand116({
35746
+ var flowsCommand = defineCommand117({
34956
35747
  meta: {
34957
35748
  name: "flows",
34958
35749
  description: `Read and shape this workspace's Forms (flows).
@@ -34986,10 +35777,10 @@ Full guide: __tooling__/docs/tools/baker/flows.md`
34986
35777
  });
34987
35778
 
34988
35779
  // src/commands/ga4/index.ts
34989
- import { defineCommand as defineCommand123 } from "citty";
35780
+ import { defineCommand as defineCommand124 } from "citty";
34990
35781
 
34991
35782
  // src/commands/ga4/audit.ts
34992
- import { defineCommand as defineCommand117 } from "citty";
35783
+ import { defineCommand as defineCommand118 } from "citty";
34993
35784
 
34994
35785
  // src/commands/ga4/resolve.ts
34995
35786
  async function fetchProperties(useCache = true) {
@@ -35055,7 +35846,7 @@ registerSchema({
35055
35846
  "no-cache": { type: "boolean", description: "Skip cache, hit API directly", required: false }
35056
35847
  }
35057
35848
  });
35058
- var auditCommand2 = defineCommand117({
35849
+ var auditCommand2 = defineCommand118({
35059
35850
  meta: {
35060
35851
  name: "audit",
35061
35852
  description: `Run all GA4 admin health checks. Returns property config with playbook warnings.
@@ -35110,7 +35901,7 @@ Examples:
35110
35901
  });
35111
35902
 
35112
35903
  // src/commands/ga4/config.ts
35113
- import { defineCommand as defineCommand118 } from "citty";
35904
+ import { defineCommand as defineCommand119 } from "citty";
35114
35905
 
35115
35906
  // src/commands/ga4/shared.ts
35116
35907
  import { readFileSync as readFileSync12 } from "fs";
@@ -35223,10 +36014,10 @@ async function stageOps(ops) {
35223
36014
  handleError2(err);
35224
36015
  }
35225
36016
  }
35226
- async function draftAction2(path40, body, chat) {
36017
+ async function draftAction2(path43, body, chat) {
35227
36018
  const chatId = resolveChatId(chat);
35228
36019
  try {
35229
- const data = await apiPost(path40, { chatId, ...body });
36020
+ const data = await apiPost(path43, { chatId, ...body });
35230
36021
  writeJsonEnvelope({ ok: true, data });
35231
36022
  return data;
35232
36023
  } catch (err) {
@@ -35306,7 +36097,7 @@ function configHints(config) {
35306
36097
  }
35307
36098
  return hints;
35308
36099
  }
35309
- var configCommand = defineCommand118({
36100
+ var configCommand = defineCommand119({
35310
36101
  meta: {
35311
36102
  name: "config",
35312
36103
  description: `Read how the property is configured to measure \u2014 key events, custom definitions, custom events, retention
@@ -35338,7 +36129,7 @@ Examples:
35338
36129
  });
35339
36130
 
35340
36131
  // src/commands/ga4/draft.ts
35341
- import { defineCommand as defineCommand119 } from "citty";
36132
+ import { defineCommand as defineCommand120 } from "citty";
35342
36133
  registerSchema({
35343
36134
  command: "ga4.draft",
35344
36135
  description: "Review and undo the Google Analytics changes staged on this chat. Use `list` to see everything staged, `show` to inspect one change in full before the chat completes, `amend` to correct one in place, and `remove`/`clear` to drop them. `list` and `show` take --chat <id> to read an earlier chat's changes instead.",
@@ -35347,13 +36138,13 @@ registerSchema({
35347
36138
  chat: { type: "string", description: CHAT_READ_ARG.description, required: false }
35348
36139
  }
35349
36140
  });
35350
- var draftCommand3 = defineCommand119({
36141
+ var draftCommand3 = defineCommand120({
35351
36142
  meta: {
35352
36143
  name: "draft",
35353
36144
  description: "List, show, amend, remove or clear the Google Analytics changes staged on this chat. `list` and `show` take --chat <id> to read an earlier chat's changes instead."
35354
36145
  },
35355
36146
  subCommands: {
35356
- list: defineCommand119({
36147
+ list: defineCommand120({
35357
36148
  meta: { name: "list", description: "Review everything staged on this chat (--json for the raw envelope)" },
35358
36149
  args: {
35359
36150
  json: { type: "boolean", description: "Print the raw JSON envelope", required: false },
@@ -35363,7 +36154,7 @@ var draftCommand3 = defineCommand119({
35363
36154
  await draftList(args.json === true, args.chat);
35364
36155
  }
35365
36156
  }),
35366
- show: defineCommand119({
36157
+ show: defineCommand120({
35367
36158
  meta: {
35368
36159
  name: "show",
35369
36160
  description: "Print the full staged payload for one change, alongside how the property looks today \u2014 the receipt to verify it before the chat completes (never truncated)."
@@ -35380,7 +36171,7 @@ var draftCommand3 = defineCommand119({
35380
36171
  );
35381
36172
  }
35382
36173
  }),
35383
- amend: defineCommand119({
36174
+ amend: defineCommand120({
35384
36175
  meta: {
35385
36176
  name: "amend",
35386
36177
  description: "Update a staged change in place \u2014 merges a JSON patch into its payload (objects deep-merge, null deletes a key, arrays/scalars replace) and re-runs every check. Use this instead of remove + re-create."
@@ -35397,7 +36188,7 @@ var draftCommand3 = defineCommand119({
35397
36188
  });
35398
36189
  }
35399
36190
  }),
35400
- remove: defineCommand119({
36191
+ remove: defineCommand120({
35401
36192
  meta: { name: "remove", description: "Remove one staged change" },
35402
36193
  args: { ref: { type: "positional", description: "Staged ref or target id", required: false } },
35403
36194
  run: async ({ args }) => {
@@ -35406,7 +36197,7 @@ var draftCommand3 = defineCommand119({
35406
36197
  });
35407
36198
  }
35408
36199
  }),
35409
- clear: defineCommand119({
36200
+ clear: defineCommand120({
35410
36201
  meta: { name: "clear", description: "Discard all Google Analytics changes staged on this chat" },
35411
36202
  run: async () => {
35412
36203
  await draftAction2("/api/ga4/draft/clear", {});
@@ -35416,7 +36207,7 @@ var draftCommand3 = defineCommand119({
35416
36207
  });
35417
36208
 
35418
36209
  // src/commands/ga4/properties.ts
35419
- import { defineCommand as defineCommand120 } from "citty";
36210
+ import { defineCommand as defineCommand121 } from "citty";
35420
36211
  registerSchema({
35421
36212
  command: "ga4.properties",
35422
36213
  description: "List the GA4 properties this company connected \u2014 there can be several, and every one of them is yours to query. Returns the property IDs the query and audit commands take. Run this first to find property IDs.",
@@ -35432,7 +36223,7 @@ function propertyHints(properties) {
35432
36223
  resources: properties.map((property) => ({ id: property.externalId, label: property.name }))
35433
36224
  });
35434
36225
  }
35435
- var propertiesCommand = defineCommand120({
36226
+ var propertiesCommand = defineCommand121({
35436
36227
  meta: {
35437
36228
  name: "properties",
35438
36229
  description: `List accessible GA4 properties.
@@ -35482,7 +36273,7 @@ Examples:
35482
36273
  // src/commands/ga4/query.ts
35483
36274
  import { appendFileSync as appendFileSync2, existsSync as existsSync7, readFileSync as readFileSync13, writeFileSync as writeFileSync4 } from "fs";
35484
36275
  import { resolve as resolve2 } from "path";
35485
- import { defineCommand as defineCommand121 } from "citty";
36276
+ import { defineCommand as defineCommand122 } from "citty";
35486
36277
 
35487
36278
  // src/commands/ga4/presets.ts
35488
36279
  var GA4_PRESETS = [
@@ -35617,7 +36408,7 @@ function handleError3(err) {
35617
36408
  });
35618
36409
  process.exit(1);
35619
36410
  }
35620
- var queryCommand2 = defineCommand121({
36411
+ var queryCommand2 = defineCommand122({
35621
36412
  meta: {
35622
36413
  name: "query",
35623
36414
  description: `Run GA4 Data API reports. Preset-first with free-form escape hatch.
@@ -35688,7 +36479,7 @@ Free-form (escape hatch):
35688
36479
  });
35689
36480
 
35690
36481
  // src/commands/ga4/write-commands.ts
35691
- import { defineCommand as defineCommand122 } from "citty";
36482
+ import { defineCommand as defineCommand123 } from "citty";
35692
36483
  var PROPERTY_ARG_DESCRIPTION = "GA4 property id (optional only when one property is connected \u2014 run `baker ga4 properties`)";
35693
36484
  var propertyArg = { type: "string", description: PROPERTY_ARG_DESCRIPTION, required: false };
35694
36485
  function createJsonDescription(noun) {
@@ -35706,7 +36497,7 @@ registerSchema({
35706
36497
  "property-id": { type: "string", description: PROPERTY_ARG_DESCRIPTION, required: false }
35707
36498
  }
35708
36499
  });
35709
- var keyEventCommand = defineCommand122({
36500
+ var keyEventCommand = defineCommand123({
35710
36501
  meta: {
35711
36502
  name: "key-event",
35712
36503
  description: `Stage key-event (conversion) changes
@@ -35719,7 +36510,7 @@ Examples:
35719
36510
  baker ga4 key-event delete 4185`
35720
36511
  },
35721
36512
  subCommands: {
35722
- create: defineCommand122({
36513
+ create: defineCommand123({
35723
36514
  meta: {
35724
36515
  name: "create",
35725
36516
  description: "Stage a new key event. Needs eventName and countingMethod (ONCE_PER_EVENT or ONCE_PER_SESSION); defaultValue sets a monetary value when the event does not send one."
@@ -35740,7 +36531,7 @@ Examples:
35740
36531
  );
35741
36532
  }
35742
36533
  }),
35743
- update: defineCommand122({
36534
+ update: defineCommand123({
35744
36535
  meta: {
35745
36536
  name: "update",
35746
36537
  description: "Stage a change to an existing key event (pass its id). Only countingMethod and defaultValue can change \u2014 Google will not rename a key event."
@@ -35760,7 +36551,7 @@ Examples:
35760
36551
  });
35761
36552
  }
35762
36553
  }),
35763
- delete: defineCommand122({
36554
+ delete: defineCommand123({
35764
36555
  meta: {
35765
36556
  name: "delete",
35766
36557
  description: "Stage removing a key event, so the event stops counting as a conversion. The event itself keeps being collected. Irreversible once the chat completes \u2014 confirm with the user first."
@@ -35808,7 +36599,7 @@ for (const { kind, noun, createHint } of DEFINITIONS) {
35808
36599
  }
35809
36600
  function definitionCommand(definition) {
35810
36601
  const { command, kind, noun, example } = definition;
35811
- return defineCommand122({
36602
+ return defineCommand123({
35812
36603
  meta: {
35813
36604
  name: command,
35814
36605
  description: `Stage ${noun} changes
@@ -35820,7 +36611,7 @@ Examples:
35820
36611
  baker ga4 ${command} archive 12`
35821
36612
  },
35822
36613
  subCommands: {
35823
- create: defineCommand122({
36614
+ create: defineCommand123({
35824
36615
  meta: { name: "create", description: `Stage a new ${noun} (or a JSON array of them, staged together)` },
35825
36616
  args: {
35826
36617
  json: { type: "string", description: createJsonDescription(noun), required: false },
@@ -35838,7 +36629,7 @@ Examples:
35838
36629
  );
35839
36630
  }
35840
36631
  }),
35841
- update: defineCommand122({
36632
+ update: defineCommand123({
35842
36633
  meta: {
35843
36634
  name: "update",
35844
36635
  description: `Stage a change to an existing ${noun} (pass its id). The parameter name and scope cannot change \u2014 archive and recreate for that.`
@@ -35858,7 +36649,7 @@ Examples:
35858
36649
  });
35859
36650
  }
35860
36651
  }),
35861
- archive: defineCommand122({
36652
+ archive: defineCommand123({
35862
36653
  meta: {
35863
36654
  name: "archive",
35864
36655
  description: `Stage archiving a ${noun} (pass its id). It stops collecting and leaves reporting; past data stays. Irreversible once the chat completes \u2014 confirm with the user first.`
@@ -35903,7 +36694,7 @@ var dataStreamArg = {
35903
36694
  function withStream(args) {
35904
36695
  return typeof args["data-stream"] === "string" ? { dataStream: args["data-stream"] } : {};
35905
36696
  }
35906
- var customEventCommand = defineCommand122({
36697
+ var customEventCommand = defineCommand123({
35907
36698
  meta: {
35908
36699
  name: "custom-event",
35909
36700
  description: `Stage custom events \u2014 new events built from events the site already sends
@@ -35914,7 +36705,7 @@ Examples:
35914
36705
  baker ga4 custom-event delete 7`
35915
36706
  },
35916
36707
  subCommands: {
35917
- create: defineCommand122({
36708
+ create: defineCommand123({
35918
36709
  meta: {
35919
36710
  name: "create",
35920
36711
  description: "Stage a new custom event. Conditions match the source event: use field `event_name` to match the event itself, or any parameter name to match its value."
@@ -35937,7 +36728,7 @@ Examples:
35937
36728
  );
35938
36729
  }
35939
36730
  }),
35940
- update: defineCommand122({
36731
+ update: defineCommand123({
35941
36732
  meta: { name: "update", description: "Stage a change to an existing custom event (pass its id)" },
35942
36733
  args: {
35943
36734
  id: { type: "positional", description: "Custom event rule id", required: false },
@@ -35956,7 +36747,7 @@ Examples:
35956
36747
  });
35957
36748
  }
35958
36749
  }),
35959
- delete: defineCommand122({
36750
+ delete: defineCommand123({
35960
36751
  meta: {
35961
36752
  name: "delete",
35962
36753
  description: "Stage removing a custom event (pass its id). The event stops being created from then on; data already collected under it stays. Confirm with the user first."
@@ -36011,7 +36802,7 @@ function booleanArg(input, flag) {
36011
36802
  if (input === "false") return false;
36012
36803
  return failValidation3(`--${flag} must be true or false`);
36013
36804
  }
36014
- var dataRetentionCommand = defineCommand122({
36805
+ var dataRetentionCommand = defineCommand123({
36015
36806
  meta: {
36016
36807
  name: "data-retention",
36017
36808
  description: `Stage a change to how long Google Analytics keeps data
@@ -36021,7 +36812,7 @@ Examples:
36021
36812
  baker ga4 data-retention set --event-data 14 --user-data 14 --reset-on-activity true`
36022
36813
  },
36023
36814
  subCommands: {
36024
- set: defineCommand122({
36815
+ set: defineCommand123({
36025
36816
  meta: { name: "set", description: "Stage the retention window (months)" },
36026
36817
  args: {
36027
36818
  "event-data": { type: "string", description: "2, 14, 26, 38 or 50", required: false },
@@ -36057,7 +36848,7 @@ registerSchema({
36057
36848
  "property-id": { type: "string", description: PROPERTY_ARG_DESCRIPTION, required: false }
36058
36849
  }
36059
36850
  });
36060
- var audienceCommand = defineCommand122({
36851
+ var audienceCommand = defineCommand123({
36061
36852
  meta: {
36062
36853
  name: "audience",
36063
36854
  description: `Stage audience changes (saved groups of users, for retargeting and analysis)
@@ -36089,7 +36880,7 @@ Examples:
36089
36880
  baker ga4 audience archive 8813`
36090
36881
  },
36091
36882
  subCommands: {
36092
- create: defineCommand122({
36883
+ create: defineCommand123({
36093
36884
  meta: { name: "create", description: createJsonDescription("audience") },
36094
36885
  args: {
36095
36886
  json: { type: "string", description: createJsonDescription("audience"), required: false },
@@ -36107,7 +36898,7 @@ Examples:
36107
36898
  );
36108
36899
  }
36109
36900
  }),
36110
- update: defineCommand122({
36901
+ update: defineCommand123({
36111
36902
  meta: {
36112
36903
  name: "update",
36113
36904
  description: "Stage a new name or description (pass the audience id). Who is in it cannot be changed."
@@ -36128,7 +36919,7 @@ Examples:
36128
36919
  });
36129
36920
  }
36130
36921
  }),
36131
- archive: defineCommand122({
36922
+ archive: defineCommand123({
36132
36923
  meta: {
36133
36924
  name: "archive",
36134
36925
  description: "Stage retiring an audience. Cannot be undone \u2014 the users it gathered do not come back."
@@ -36157,7 +36948,7 @@ registerSchema({
36157
36948
  "property-id": { type: "string", description: PROPERTY_ARG_DESCRIPTION, required: false }
36158
36949
  }
36159
36950
  });
36160
- var propertyCommand = defineCommand122({
36951
+ var propertyCommand = defineCommand123({
36161
36952
  meta: {
36162
36953
  name: "property",
36163
36954
  description: `Stage a change to the property's own settings
@@ -36167,7 +36958,7 @@ Examples:
36167
36958
  baker ga4 property set --name "Acme \u2014 Web" --industry REAL_ESTATE`
36168
36959
  },
36169
36960
  subCommands: {
36170
- set: defineCommand122({
36961
+ set: defineCommand123({
36171
36962
  meta: { name: "set", description: "Stage the property name and/or industry category" },
36172
36963
  args: {
36173
36964
  name: { type: "string", description: "New property display name", required: false },
@@ -36207,7 +36998,7 @@ registerSchema({
36207
36998
  "property-id": { type: "string", description: PROPERTY_ARG_DESCRIPTION, required: false }
36208
36999
  }
36209
37000
  });
36210
- var adsLinkCommand = defineCommand122({
37001
+ var adsLinkCommand = defineCommand123({
36211
37002
  meta: {
36212
37003
  name: "ads-link",
36213
37004
  description: `Stage linking this property to a Google Ads account
@@ -36222,7 +37013,7 @@ Examples:
36222
37013
  baker ga4 ads-link delete L1234`
36223
37014
  },
36224
37015
  subCommands: {
36225
- create: defineCommand122({
37016
+ create: defineCommand123({
36226
37017
  meta: { name: "create", description: "Stage linking the property to a Google Ads account" },
36227
37018
  args: {
36228
37019
  "customer-id": { type: "string", description: "Google Ads customer id (123-456-7890)", required: false },
@@ -36241,7 +37032,7 @@ Examples:
36241
37032
  await stageOp3({ kind: "ga4.googleAdsLink.create", payload, ...withProperty(args) });
36242
37033
  }
36243
37034
  }),
36244
- update: defineCommand122({
37035
+ update: defineCommand123({
36245
37036
  meta: {
36246
37037
  name: "update",
36247
37038
  description: "Stage the ads-personalisation setting on an existing link (pass its id)"
@@ -36263,7 +37054,7 @@ Examples:
36263
37054
  });
36264
37055
  }
36265
37056
  }),
36266
- delete: defineCommand122({
37057
+ delete: defineCommand123({
36267
37058
  meta: {
36268
37059
  name: "delete",
36269
37060
  description: "Stage unlinking. Stops conversion imports and strands every audience on this property."
@@ -36314,7 +37105,7 @@ var MEASUREMENT_FLAGS = {
36314
37105
  "page-changes": "pageChangesEnabled",
36315
37106
  "form-interactions": "formInteractionsEnabled"
36316
37107
  };
36317
- var enhancedMeasurementCommand = defineCommand122({
37108
+ var enhancedMeasurementCommand = defineCommand123({
36318
37109
  meta: {
36319
37110
  name: "enhanced-measurement",
36320
37111
  description: `Stage the automatic events GA4 collects on a website stream
@@ -36328,7 +37119,7 @@ Examples:
36328
37119
  baker ga4 enhanced-measurement set --video false`
36329
37120
  },
36330
37121
  subCommands: {
36331
- set: defineCommand122({
37122
+ set: defineCommand123({
36332
37123
  meta: { name: "set", description: "Stage which automatic events the website stream collects" },
36333
37124
  args: {
36334
37125
  scrolls: { type: "string", description: "true|false", required: false },
@@ -36371,7 +37162,7 @@ Examples:
36371
37162
  });
36372
37163
 
36373
37164
  // src/commands/ga4/index.ts
36374
- var ga4Command = defineCommand123({
37165
+ var ga4Command = defineCommand124({
36375
37166
  meta: {
36376
37167
  name: "ga4",
36377
37168
  description: `Google Analytics 4. Report on a property, audit its config, and change what it measures.
@@ -36420,12 +37211,12 @@ Full guide: __tooling__/docs/tools/baker/ga4.md`
36420
37211
  });
36421
37212
 
36422
37213
  // src/commands/gsc/index.ts
36423
- import { defineCommand as defineCommand127 } from "citty";
37214
+ import { defineCommand as defineCommand128 } from "citty";
36424
37215
 
36425
37216
  // src/commands/gsc/query.ts
36426
37217
  import { appendFileSync as appendFileSync3, existsSync as existsSync8, readFileSync as readFileSync14, writeFileSync as writeFileSync5 } from "fs";
36427
37218
  import { resolve as resolve3 } from "path";
36428
- import { defineCommand as defineCommand124 } from "citty";
37219
+ import { defineCommand as defineCommand125 } from "citty";
36429
37220
 
36430
37221
  // src/commands/gsc/presets.ts
36431
37222
  var GSC_PRESETS = [
@@ -36619,7 +37410,7 @@ function handleError4(err) {
36619
37410
  });
36620
37411
  process.exit(1);
36621
37412
  }
36622
- var queryCommand3 = defineCommand124({
37413
+ var queryCommand3 = defineCommand125({
36623
37414
  meta: {
36624
37415
  name: "query",
36625
37416
  description: `Run GSC Search Analytics queries. Preset-first with free-form escape hatch.
@@ -36697,7 +37488,7 @@ Free-form (escape hatch):
36697
37488
  });
36698
37489
 
36699
37490
  // src/commands/gsc/sitemaps.ts
36700
- import { defineCommand as defineCommand125 } from "citty";
37491
+ import { defineCommand as defineCommand126 } from "citty";
36701
37492
  registerSchema({
36702
37493
  command: "gsc.sitemaps",
36703
37494
  description: "List sitemaps for a Search Console site. Check sitemap health and errors.",
@@ -36706,7 +37497,7 @@ registerSchema({
36706
37497
  "no-cache": { type: "boolean", description: "Skip cache, hit API directly", required: false }
36707
37498
  }
36708
37499
  });
36709
- var sitemapsCommand = defineCommand125({
37500
+ var sitemapsCommand = defineCommand126({
36710
37501
  meta: {
36711
37502
  name: "sitemaps",
36712
37503
  description: `List sitemaps for a site. Check health and errors.
@@ -36759,7 +37550,7 @@ Examples:
36759
37550
  });
36760
37551
 
36761
37552
  // src/commands/gsc/sites.ts
36762
- import { defineCommand as defineCommand126 } from "citty";
37553
+ import { defineCommand as defineCommand127 } from "citty";
36763
37554
  registerSchema({
36764
37555
  command: "gsc.sites",
36765
37556
  description: "List the Search Console sites this company connected \u2014 there can be several, and every one of them is yours to query. Returns the site URLs the query and sitemaps commands take.",
@@ -36775,7 +37566,7 @@ function siteHints(sites) {
36775
37566
  resources: sites.map((site) => ({ id: site.siteUrl, label: site.permissionLevel }))
36776
37567
  });
36777
37568
  }
36778
- var sitesCommand = defineCommand126({
37569
+ var sitesCommand = defineCommand127({
36779
37570
  meta: {
36780
37571
  name: "sites",
36781
37572
  description: `List verified Search Console sites.
@@ -36823,7 +37614,7 @@ Examples:
36823
37614
  });
36824
37615
 
36825
37616
  // src/commands/gsc/index.ts
36826
- var gscCommand = defineCommand127({
37617
+ var gscCommand = defineCommand128({
36827
37618
  meta: {
36828
37619
  name: "gsc",
36829
37620
  description: `Google Search Console commands. PPC-SEO arbitrage, brand halo analysis, negative keyword discovery.
@@ -36847,7 +37638,7 @@ Full guide: __tooling__/docs/tools/baker/gsc.md`
36847
37638
  });
36848
37639
 
36849
37640
  // src/commands/history/index.ts
36850
- import { defineCommand as defineCommand128 } from "citty";
37641
+ import { defineCommand as defineCommand129 } from "citty";
36851
37642
  registerSchema({
36852
37643
  command: "history.list",
36853
37644
  description: "Start here: unified account history (audit log) \u2014 everything that changed on this account, newest first: publishes, chat lifecycle, backlog actions, team changes, setup links, tags, schedules, ad-platform writes, followed advertisers, media, creatives, reports, domains, and integrations. Use it to see what happened recently before planning work. Compact by default; add --full for raw metadata per entry.",
@@ -36902,7 +37693,7 @@ function parseBoundedInt2(raw, name, min, max) {
36902
37693
  }
36903
37694
  return value;
36904
37695
  }
36905
- var listCommand13 = defineCommand128({
37696
+ var listCommand13 = defineCommand129({
36906
37697
  meta: {
36907
37698
  name: "list",
36908
37699
  description: "List recent account changes (unified audit log), newest first."
@@ -36948,7 +37739,7 @@ var listCommand13 = defineCommand128({
36948
37739
  }
36949
37740
  }
36950
37741
  });
36951
- var historyCommand = defineCommand128({
37742
+ var historyCommand = defineCommand129({
36952
37743
  meta: {
36953
37744
  name: "history",
36954
37745
  description: `Unified account history (audit log): what changed, who did it, and when.
@@ -36958,7 +37749,7 @@ Full guide: __tooling__/docs/tools/baker/history.md`
36958
37749
  });
36959
37750
 
36960
37751
  // src/commands/hubspot/index.ts
36961
- import { defineCommand as defineCommand129 } from "citty";
37752
+ import { defineCommand as defineCommand130 } from "citty";
36962
37753
  var EMBED_TYPES = ["legacy", "v4", "unknown"];
36963
37754
  function failNotConnected(err) {
36964
37755
  if (err instanceof ApiError && err.code === "FORBIDDEN" && err.message.includes(HUBSPOT_MISSING_GRANT_MARKER)) {
@@ -37112,7 +37903,7 @@ registerSchema({
37112
37903
  }
37113
37904
  }
37114
37905
  });
37115
- var formsListCommand = defineCommand129({
37906
+ var formsListCommand = defineCommand130({
37116
37907
  meta: {
37117
37908
  name: "list",
37118
37909
  description: "List HubSpot forms with embed type and post-submit action. Example: baker hubspot forms list --redirecting-only"
@@ -37157,7 +37948,7 @@ var formsListCommand = defineCommand129({
37157
37948
  }
37158
37949
  }
37159
37950
  });
37160
- var formsViewCommand = defineCommand129({
37951
+ var formsViewCommand = defineCommand130({
37161
37952
  meta: {
37162
37953
  name: "view",
37163
37954
  description: "Show one HubSpot form's fields and configuration. Example: baker hubspot forms view 1a2b3c"
@@ -37209,7 +38000,7 @@ var formsViewCommand = defineCommand129({
37209
38000
  }
37210
38001
  }
37211
38002
  });
37212
- var meetingsListCommand = defineCommand129({
38003
+ var meetingsListCommand = defineCommand130({
37213
38004
  meta: {
37214
38005
  name: "list",
37215
38006
  description: "List HubSpot meeting links (calendars). Example: baker hubspot meetings list"
@@ -37251,7 +38042,7 @@ var meetingsListCommand = defineCommand129({
37251
38042
  }
37252
38043
  }
37253
38044
  });
37254
- var meetingsViewCommand = defineCommand129({
38045
+ var meetingsViewCommand = defineCommand130({
37255
38046
  meta: {
37256
38047
  name: "view",
37257
38048
  description: "Show one HubSpot meeting link's booking fields. Example: baker hubspot meetings view discovery-call"
@@ -37295,7 +38086,7 @@ var meetingsViewCommand = defineCommand129({
37295
38086
  }
37296
38087
  }
37297
38088
  });
37298
- var formsSubmissionsCommand = defineCommand129({
38089
+ var formsSubmissionsCommand = defineCommand130({
37299
38090
  meta: {
37300
38091
  name: "submissions",
37301
38092
  description: "How many leads a form received, and when. Example: baker hubspot forms submissions <formId> --days 30"
@@ -37344,7 +38135,7 @@ var formsSubmissionsCommand = defineCommand129({
37344
38135
  }
37345
38136
  }
37346
38137
  });
37347
- var workflowsListCommand = defineCommand129({
38138
+ var workflowsListCommand = defineCommand130({
37348
38139
  meta: {
37349
38140
  name: "list",
37350
38141
  description: "List HubSpot workflows. Example: baker hubspot workflows list --enabled-only"
@@ -37409,7 +38200,7 @@ function workflowViewHints(workflow) {
37409
38200
  }
37410
38201
  return hints;
37411
38202
  }
37412
- var workflowsViewCommand = defineCommand129({
38203
+ var workflowsViewCommand = defineCommand130({
37413
38204
  meta: {
37414
38205
  name: "view",
37415
38206
  description: "Read one workflow's real configuration \u2014 enrolment, branches in order, and what each step writes. Example: baker hubspot workflows view 121183594 --full"
@@ -37436,7 +38227,7 @@ var workflowsViewCommand = defineCommand129({
37436
38227
  }
37437
38228
  }
37438
38229
  });
37439
- var pipelinesListCommand = defineCommand129({
38230
+ var pipelinesListCommand = defineCommand130({
37440
38231
  meta: {
37441
38232
  name: "list",
37442
38233
  description: "List HubSpot deal pipelines and their stages. Example: baker hubspot pipelines list"
@@ -37453,7 +38244,7 @@ var pipelinesListCommand = defineCommand129({
37453
38244
  }
37454
38245
  }
37455
38246
  });
37456
- var contactsSummaryCommand = defineCommand129({
38247
+ var contactsSummaryCommand = defineCommand130({
37457
38248
  meta: {
37458
38249
  name: "summary",
37459
38250
  description: "Whether recent leads are being worked, as counts. Example: baker hubspot contacts summary --days 30"
@@ -37523,7 +38314,7 @@ function contactLookupHints(data) {
37523
38314
  }
37524
38315
  return hints;
37525
38316
  }
37526
- var contactsLookupCommand = defineCommand129({
38317
+ var contactsLookupCommand = defineCommand130({
37527
38318
  meta: {
37528
38319
  name: "lookup",
37529
38320
  description: "Find one contact by email and see whether it was worked. Example: baker hubspot contacts lookup a@b.com"
@@ -37547,27 +38338,27 @@ var contactsLookupCommand = defineCommand129({
37547
38338
  }
37548
38339
  }
37549
38340
  });
37550
- var contactsCommand = defineCommand129({
38341
+ var contactsCommand = defineCommand130({
37551
38342
  meta: { name: "contacts", description: "Contacts on the connected HubSpot account." },
37552
38343
  subCommands: { summary: contactsSummaryCommand, lookup: contactsLookupCommand }
37553
38344
  });
37554
- var formsCommand = defineCommand129({
38345
+ var formsCommand = defineCommand130({
37555
38346
  meta: { name: "forms", description: "HubSpot forms on the connected account." },
37556
38347
  subCommands: { list: formsListCommand, view: formsViewCommand, submissions: formsSubmissionsCommand }
37557
38348
  });
37558
- var workflowsCommand = defineCommand129({
38349
+ var workflowsCommand = defineCommand130({
37559
38350
  meta: { name: "workflows", description: "HubSpot workflows on the connected account." },
37560
38351
  subCommands: { list: workflowsListCommand, view: workflowsViewCommand }
37561
38352
  });
37562
- var pipelinesCommand = defineCommand129({
38353
+ var pipelinesCommand = defineCommand130({
37563
38354
  meta: { name: "pipelines", description: "HubSpot deal pipelines on the connected account." },
37564
38355
  subCommands: { list: pipelinesListCommand }
37565
38356
  });
37566
- var meetingsCommand = defineCommand129({
38357
+ var meetingsCommand = defineCommand130({
37567
38358
  meta: { name: "meetings", description: "HubSpot meeting links (calendars) on the connected account." },
37568
38359
  subCommands: { list: meetingsListCommand, view: meetingsViewCommand }
37569
38360
  });
37570
- var hubspotCommand = defineCommand129({
38361
+ var hubspotCommand = defineCommand130({
37571
38362
  meta: {
37572
38363
  name: "hubspot",
37573
38364
  description: `Read the connected HubSpot account \u2014 forms, the leads they received, workflows, deal pipelines and
@@ -37605,10 +38396,10 @@ Full guide: __tooling__/docs/tools/baker/hubspot.md`
37605
38396
  });
37606
38397
 
37607
38398
  // src/commands/images/index.ts
37608
- import { defineCommand as defineCommand155 } from "citty";
38399
+ import { defineCommand as defineCommand156 } from "citty";
37609
38400
 
37610
38401
  // src/commands/images/crop.ts
37611
- import { defineCommand as defineCommand130 } from "citty";
38402
+ import { defineCommand as defineCommand131 } from "citty";
37612
38403
 
37613
38404
  // src/lib/image/crop-sprite.ts
37614
38405
  import sharp from "sharp";
@@ -37623,7 +38414,7 @@ function cropSprite(input, region) {
37623
38414
 
37624
38415
  // src/lib/image/io.ts
37625
38416
  import { randomBytes } from "crypto";
37626
- import { glob as fsGlob, readFile as readFile20, rename, stat as stat4, writeFile as writeFile11 } from "fs/promises";
38417
+ import { glob as fsGlob, readFile as readFile21, rename, stat as stat5, writeFile as writeFile11 } from "fs/promises";
37627
38418
  import { dirname as dirname2, extname as extname2, join as join5, resolve as resolve4 } from "path";
37628
38419
  var REMOTE_RE = /^https?:\/\//i;
37629
38420
  var GLOB_RE = /[*?[\]{}]/;
@@ -37656,11 +38447,11 @@ async function readImageBuffer(pathOrUrl) {
37656
38447
  const { buffer } = await fetchExternalBytes(pathOrUrl, { maxBytes: MAX_REMOTE_IMAGE_BYTES });
37657
38448
  return buffer;
37658
38449
  }
37659
- return readFile20(pathOrUrl);
38450
+ return readFile21(pathOrUrl);
37660
38451
  }
37661
- async function isDirectory(path40) {
38452
+ async function isDirectory(path43) {
37662
38453
  try {
37663
- const s = await stat4(path40);
38454
+ const s = await stat5(path43);
37664
38455
  return s.isDirectory();
37665
38456
  } catch {
37666
38457
  return false;
@@ -37730,7 +38521,7 @@ function emitError2(err) {
37730
38521
  }
37731
38522
  process.exit(1);
37732
38523
  }
37733
- var cropCommand = defineCommand130({
38524
+ var cropCommand = defineCommand131({
37734
38525
  meta: {
37735
38526
  name: "crop",
37736
38527
  description: "Crop a rectangular region from an image.\n\nExample: baker images crop sprite.png --x 0 --y 0 --width 64 --height 64 --output icon.png"
@@ -37766,7 +38557,7 @@ var cropCommand = defineCommand130({
37766
38557
  });
37767
38558
 
37768
38559
  // src/commands/images/delete.ts
37769
- import { defineCommand as defineCommand131 } from "citty";
38560
+ import { defineCommand as defineCommand132 } from "citty";
37770
38561
  registerSchema({
37771
38562
  command: "images.delete",
37772
38563
  description: "Delete an image by ID",
@@ -37780,7 +38571,7 @@ registerSchema({
37780
38571
  }
37781
38572
  }
37782
38573
  });
37783
- var deleteCommand2 = defineCommand131({
38574
+ var deleteCommand2 = defineCommand132({
37784
38575
  meta: {
37785
38576
  name: "delete",
37786
38577
  description: "Delete an image by ID. Use --dry-run to preview. Example: baker images delete j571abc123 --dry-run"
@@ -37821,7 +38612,7 @@ var deleteCommand2 = defineCommand131({
37821
38612
  });
37822
38613
 
37823
38614
  // src/commands/images/dimensions.ts
37824
- import { defineCommand as defineCommand132 } from "citty";
38615
+ import { defineCommand as defineCommand133 } from "citty";
37825
38616
 
37826
38617
  // src/lib/image/dimensions.ts
37827
38618
  import { imageSize } from "image-size";
@@ -37850,7 +38641,7 @@ registerSchema({
37850
38641
  }
37851
38642
  }
37852
38643
  });
37853
- var dimensionsCommand = defineCommand132({
38644
+ var dimensionsCommand = defineCommand133({
37854
38645
  meta: {
37855
38646
  name: "dimensions",
37856
38647
  description: "Read image dimensions without decoding the full file.\n\nExample: baker images dimensions ./logo.png\nExample: baker images dimensions https://acme.com/hero.png"
@@ -37906,7 +38697,7 @@ var dimensionsCommand = defineCommand132({
37906
38697
  });
37907
38698
 
37908
38699
  // src/commands/images/download.ts
37909
- import { defineCommand as defineCommand133 } from "citty";
38700
+ import { defineCommand as defineCommand134 } from "citty";
37910
38701
 
37911
38702
  // src/commands/images/downloadPaths.ts
37912
38703
  import { basename as basename2, extname as extname3, join as join6 } from "path";
@@ -37962,13 +38753,13 @@ function resolveDownloadPath({ baseName, extension, out, outIsDirectory: outIsDi
37962
38753
  }
37963
38754
  function disambiguate(paths) {
37964
38755
  const taken = /* @__PURE__ */ new Set();
37965
- return paths.map((path40) => {
37966
- if (!taken.has(path40)) {
37967
- taken.add(path40);
37968
- return path40;
38756
+ return paths.map((path43) => {
38757
+ if (!taken.has(path43)) {
38758
+ taken.add(path43);
38759
+ return path43;
37969
38760
  }
37970
- const ext = extname3(path40);
37971
- const stem = path40.slice(0, path40.length - ext.length);
38761
+ const ext = extname3(path43);
38762
+ const stem = path43.slice(0, path43.length - ext.length);
37972
38763
  let n = 2;
37973
38764
  while (taken.has(`${stem}-${n}${ext}`)) n += 1;
37974
38765
  const unique = `${stem}-${n}${ext}`;
@@ -38095,10 +38886,10 @@ async function runDownloads(plan) {
38095
38886
  const paths = disambiguate(fetched.map((item) => item.path));
38096
38887
  const downloaded = [];
38097
38888
  for (const [index, item] of fetched.entries()) {
38098
- const path40 = paths[index] ?? item.path;
38889
+ const path43 = paths[index] ?? item.path;
38099
38890
  try {
38100
- await atomicWrite(path40, item.buffer);
38101
- downloaded.push({ input: item.input, output: path40, bytes: item.buffer.length, contentType: item.contentType });
38891
+ await atomicWrite(path43, item.buffer);
38892
+ downloaded.push({ input: item.input, output: path43, bytes: item.buffer.length, contentType: item.contentType });
38102
38893
  } catch (err) {
38103
38894
  failed.push({ input: item.input, error: failureMessage(err, "Write failed") });
38104
38895
  }
@@ -38131,7 +38922,7 @@ function emitError3(err) {
38131
38922
  writeJson({ ok: false, error: { code: "INTERNAL_ERROR", message: "Unexpected error" } });
38132
38923
  process.exit(1);
38133
38924
  }
38134
- var downloadCommand = defineCommand133({
38925
+ var downloadCommand = defineCommand134({
38135
38926
  meta: {
38136
38927
  name: "download",
38137
38928
  description: "Download image URLs and/or library images to local files \u2014 the missing first half of `source \u2192 download \u2192 normalize \u2192 place`. Never use `curl` for this.\n\nExamples:\n baker images download https://media.withbaker.com/\u2026/logo.webp\n baker images download j57abc123 j57def456 --out src/pages/pricing/_images/\n baker images download https://\u2026/hero.png --out ./hero.png"
@@ -38164,7 +38955,7 @@ var downloadCommand = defineCommand133({
38164
38955
  });
38165
38956
 
38166
38957
  // src/commands/images/extract.ts
38167
- import { defineCommand as defineCommand134 } from "citty";
38958
+ import { defineCommand as defineCommand135 } from "citty";
38168
38959
 
38169
38960
  // src/commands/images/autoIngest.ts
38170
38961
  var AUTO_INGEST_MAX = {
@@ -38211,7 +39002,7 @@ registerSchema({
38211
39002
  }
38212
39003
  }
38213
39004
  });
38214
- var extractCommand = defineCommand134({
39005
+ var extractCommand = defineCommand135({
38215
39006
  meta: {
38216
39007
  name: "extract",
38217
39008
  description: "Pull every image from a single URL via Firecrawl. ~$0.001/scrape. Cap auto-ingest at 20.\n\nExample: baker images extract https://stripe.com --auto-ingest 5"
@@ -38266,7 +39057,7 @@ var extractCommand = defineCommand134({
38266
39057
  });
38267
39058
 
38268
39059
  // src/commands/images/find.ts
38269
- import { defineCommand as defineCommand135 } from "citty";
39060
+ import { defineCommand as defineCommand136 } from "citty";
38270
39061
 
38271
39062
  // src/commands/images/providerHits.ts
38272
39063
  function asRecord3(value) {
@@ -38404,7 +39195,7 @@ registerSchema({
38404
39195
  full: { type: "boolean", description: "Include full metadata", required: false, default: false }
38405
39196
  }
38406
39197
  });
38407
- var findCommand = defineCommand135({
39198
+ var findCommand = defineCommand136({
38408
39199
  meta: {
38409
39200
  name: "find",
38410
39201
  description: "Library-first fanout image search. Opt in to providers with --sources. `--fallback` short-circuits to externals only when library is thin. With --auto-ingest, ingested external hits return Baker-owned URLs.\n\nExample: baker images find 'office' --sources library,magnific --limit 20"
@@ -38477,7 +39268,7 @@ var findCommand = defineCommand135({
38477
39268
  });
38478
39269
 
38479
39270
  // src/commands/images/get.ts
38480
- import { defineCommand as defineCommand136 } from "citty";
39271
+ import { defineCommand as defineCommand137 } from "citty";
38481
39272
  registerSchema({
38482
39273
  command: "images.get",
38483
39274
  description: "Get a single image by ID",
@@ -38485,7 +39276,7 @@ registerSchema({
38485
39276
  id: { type: "string", description: "Image ID", required: true }
38486
39277
  }
38487
39278
  });
38488
- var getCommand3 = defineCommand136({
39279
+ var getCommand3 = defineCommand137({
38489
39280
  meta: { name: "get", description: "Get a single image by ID. Example: baker images get j571abc123" },
38490
39281
  args: {
38491
39282
  id: { type: "positional", description: "Image ID", required: false },
@@ -38521,7 +39312,7 @@ var getCommand3 = defineCommand136({
38521
39312
  });
38522
39313
 
38523
39314
  // src/commands/images/gif.ts
38524
- import { defineCommand as defineCommand137 } from "citty";
39315
+ import { defineCommand as defineCommand138 } from "citty";
38525
39316
  registerSchema({
38526
39317
  command: "images.gif",
38527
39318
  description: "Search Giphy for GIFs / reaction memes (paid social creative).",
@@ -38553,7 +39344,7 @@ registerSchema({
38553
39344
  }
38554
39345
  }
38555
39346
  });
38556
- var gifCommand = defineCommand137({
39347
+ var gifCommand = defineCommand138({
38557
39348
  meta: {
38558
39349
  name: "gif",
38559
39350
  description: "Search Giphy for GIFs / reaction memes \u2014 built for paid-social creative (Meta, TikTok, LinkedIn, X). Free API. Each hit carries WebP + GIF + MP4 URLs in providerMeta so you can pick the right format per platform.\n\nExample: baker images gif 'this is fine' --limit 10\nExample: baker images gif 'office reaction' --rating pg --auto-ingest 2\nExample: baker images gif --trending --limit 25"
@@ -38600,7 +39391,7 @@ var gifCommand = defineCommand137({
38600
39391
  });
38601
39392
 
38602
39393
  // src/commands/images/google.ts
38603
- import { defineCommand as defineCommand138 } from "citty";
39394
+ import { defineCommand as defineCommand139 } from "citty";
38604
39395
  var GOOGLE_ERROR_FIX = {
38605
39396
  action: "use_different_resource",
38606
39397
  explanation: "Generate the asset instead of retrying Google. Google is the last-resort image provider. Run `baker studio generate` to make the asset. Never sleep-and-retry this command \u2014 the CLI already backs off on rate limits. If no image can be sourced, continue the rest of the task with a placeholder rather than aborting it."
@@ -38640,7 +39431,7 @@ registerSchema({
38640
39431
  }
38641
39432
  }
38642
39433
  });
38643
- var googleCommand2 = defineCommand138({
39434
+ var googleCommand2 = defineCommand139({
38644
39435
  meta: {
38645
39436
  name: "google",
38646
39437
  description: "Google Images via the official Custom Search JSON API ($0.005/query, free 100/day). \u26A0 Source unverified \u2014 watermarks, low-res, mislabeled results are common. Use as last resort. With --auto-ingest, ingested hits return Baker-owned URLs.\n\nExample: baker images google 'industrial workshop' --type photo --size large --limit 20"
@@ -38713,7 +39504,7 @@ var googleCommand2 = defineCommand138({
38713
39504
  });
38714
39505
 
38715
39506
  // src/commands/images/group.ts
38716
- import { defineCommand as defineCommand139 } from "citty";
39507
+ import { defineCommand as defineCommand140 } from "citty";
38717
39508
 
38718
39509
  // src/commands/mediaGroup.ts
38719
39510
  async function runMediaGroupLookup(input) {
@@ -38766,7 +39557,7 @@ registerSchema({
38766
39557
  "group-key": { type: "string", description: "The set key directly, when you already have it", required: false }
38767
39558
  }
38768
39559
  });
38769
- var groupCommand = defineCommand139({
39560
+ var groupCommand = defineCommand140({
38770
39561
  meta: {
38771
39562
  name: "group",
38772
39563
  description: "List every asset that arrived in the same set \u2014 the slides of one Instagram carousel, the images off one scraped page. Start here whenever a hit looks like part of a sequence: carousel slides are authored to be read in order and usually only make sense together. Takes an image or a video id, since one carousel can contain both. Example: baker images group <imageId>"
@@ -38786,7 +39577,7 @@ var groupCommand = defineCommand139({
38786
39577
  });
38787
39578
 
38788
39579
  // src/commands/images/icon.ts
38789
- import { defineCommand as defineCommand140 } from "citty";
39580
+ import { defineCommand as defineCommand141 } from "citty";
38790
39581
 
38791
39582
  // src/commands/images/brandVerification.ts
38792
39583
  function brandToken(domain) {
@@ -38883,7 +39674,7 @@ registerSchema({
38883
39674
  full: { type: "boolean", description: "Include full metadata", required: false, default: false }
38884
39675
  }
38885
39676
  });
38886
- var iconCommand = defineCommand140({
39677
+ var iconCommand = defineCommand141({
38887
39678
  meta: {
38888
39679
  name: "icon",
38889
39680
  description: "Icon via Iconify (simple-icons, logos, lucide, devicon, heroicons, tabler, phosphor, material-symbols, \u2026). Free CDN, no API key.\n\nExample: baker images icon react --set devicon\nExample: baker images icon lucide:check --color '#0a0a0a'"
@@ -38953,7 +39744,7 @@ var iconCommand = defineCommand140({
38953
39744
  });
38954
39745
 
38955
39746
  // src/commands/images/ingest.ts
38956
- import { defineCommand as defineCommand141 } from "citty";
39747
+ import { defineCommand as defineCommand142 } from "citty";
38957
39748
  var SOURCE_VALUES = imageSourceSchema.options.join(" | ");
38958
39749
  registerSchema({
38959
39750
  command: "images.ingest",
@@ -38968,7 +39759,7 @@ registerSchema({
38968
39759
  fields: { type: "string", description: "Comma-separated field names to include", required: false }
38969
39760
  }
38970
39761
  });
38971
- var ingestCommand = defineCommand141({
39762
+ var ingestCommand = defineCommand142({
38972
39763
  meta: {
38973
39764
  name: "ingest",
38974
39765
  description: "Download a remote URL and store it in the library. Hash-deduped on bytes + externalId.\n\nExample: baker images ingest https://img.freepik.com/free-photo/xyz.jpg --source magnific --external-id 12345"
@@ -39037,7 +39828,7 @@ var ingestCommand = defineCommand141({
39037
39828
  });
39038
39829
 
39039
39830
  // src/commands/images/layerize.ts
39040
- import { defineCommand as defineCommand142 } from "citty";
39831
+ import { defineCommand as defineCommand143 } from "citty";
39041
39832
  registerSchema({
39042
39833
  command: "images.layerize",
39043
39834
  description: "Split a library image into editable layers: transparent PNG cutouts for each element, plus any headline recovered as EDITABLE TEXT with its font, size, colour and position. Waits for completion by default. Costs credits.",
@@ -39101,7 +39892,7 @@ async function pollUntilSettled(imageId, maxWait) {
39101
39892
  }
39102
39893
  return null;
39103
39894
  }
39104
- var layerizeCommand = defineCommand142({
39895
+ var layerizeCommand = defineCommand143({
39105
39896
  meta: {
39106
39897
  name: "layerize",
39107
39898
  description: "Split a library image into editable layers \u2014 transparent cutouts per element, plus any baked-in headline recovered as editable text with its typography.\n\nStart here: baker images layerize j571abc123def\nExample: baker images layerize j571abc123def --full\nExample: baker images layerize j571abc123def --instructions 'keep the product and its shadow together'"
@@ -39170,7 +39961,7 @@ registerSchema({
39170
39961
  full: { type: "boolean", description: "Include geometry and typography for every layer", required: false }
39171
39962
  }
39172
39963
  });
39173
- var layersCommand = defineCommand142({
39964
+ var layersCommand = defineCommand143({
39174
39965
  meta: {
39175
39966
  name: "layers",
39176
39967
  description: "Read the layers of an image that has already been split. Free \u2014 no provider call.\n\nStart here: baker images layers j571abc123def\nExample: baker images layers j571abc123def --full"
@@ -39210,7 +40001,7 @@ var layersCommand = defineCommand142({
39210
40001
  });
39211
40002
 
39212
40003
  // src/commands/images/library.ts
39213
- import { defineCommand as defineCommand143 } from "citty";
40004
+ import { defineCommand as defineCommand144 } from "citty";
39214
40005
  registerSchema({
39215
40006
  command: "images.library",
39216
40007
  description: "Search the company image library. Returns only ready images.",
@@ -39236,7 +40027,7 @@ registerSchema({
39236
40027
  }
39237
40028
  }
39238
40029
  });
39239
- var libraryCommand = defineCommand143({
40030
+ var libraryCommand = defineCommand144({
39240
40031
  meta: {
39241
40032
  name: "library",
39242
40033
  description: "Search the company image library (hybrid BM25 + vector + Cohere rerank). Use this BEFORE any external provider.\n\nExample: baker images library 'hero banner' --aspect-ratio 16:9 --source magnific"
@@ -39299,7 +40090,7 @@ var libraryCommand = defineCommand143({
39299
40090
  });
39300
40091
 
39301
40092
  // src/commands/images/logo.ts
39302
- import { defineCommand as defineCommand144 } from "citty";
40093
+ import { defineCommand as defineCommand145 } from "citty";
39303
40094
  registerSchema({
39304
40095
  command: "images.logo",
39305
40096
  description: "Brand logo lookup via Brandfetch. Auto-ingests by default. Returns `brandMatch` \u2014 Brandfetch's own verdict on whose brand the domain is (confirmed | mismatch | unverified). Branch on it: `mismatch` means the mark is another company's, so do not place it. `confirmed` verifies the record, not the artwork \u2014 read the ingested row back to check the mark itself.",
@@ -39327,7 +40118,7 @@ registerSchema({
39327
40118
  full: { type: "boolean", description: "Include full metadata", required: false, default: false }
39328
40119
  }
39329
40120
  });
39330
- var logoCommand = defineCommand144({
40121
+ var logoCommand = defineCommand145({
39331
40122
  meta: {
39332
40123
  name: "logo",
39333
40124
  description: "Brand logo via Brandfetch. Returns up to 5 variants (icon, light/dark logo, light/dark symbol) plus `brandMatch`. Auto-ingests the first variant.\n\nStart here: check `brandMatch.verdict`.\n mismatch \u2192 the mark belongs to `brandMatch.name`, a different company. Do not place it.\n unverified \u2192 nothing confirmed whose logo this is. Treat as unchecked.\n confirmed \u2192 Brandfetch has this brand's record. That verifies the record, NOT the artwork \u2014 a confirmed domain has served another company's logo before.\n\n\u26A0 Whatever the verdict, read the ingested row back with `baker images get <imageId>` and check `textInImage`/`subject` before placing it. That is the only check that looks at the mark.\n\nExample: baker images logo stripe.com --variant logo"
@@ -39400,7 +40191,7 @@ var logoCommand = defineCommand144({
39400
40191
  });
39401
40192
 
39402
40193
  // src/commands/images/normalize.ts
39403
- import { defineCommand as defineCommand145 } from "citty";
40194
+ import { defineCommand as defineCommand146 } from "citty";
39404
40195
 
39405
40196
  // src/lib/image/color-changer.ts
39406
40197
  import quantize from "quantize";
@@ -40132,7 +40923,7 @@ function coerceRawArgs(args) {
40132
40923
  "dry-run": bool(args["dry-run"])
40133
40924
  };
40134
40925
  }
40135
- var normalizeCommand2 = defineCommand145({
40926
+ var normalizeCommand2 = defineCommand146({
40136
40927
  meta: {
40137
40928
  name: "normalize",
40138
40929
  description: `Normalize logos / images: declarative recolor + bg removal + trim + resize. Operates on local files; writes in-place by default.
@@ -40187,7 +40978,7 @@ Examples:
40187
40978
  });
40188
40979
 
40189
40980
  // src/commands/images/pinterest.ts
40190
- import { defineCommand as defineCommand146 } from "citty";
40981
+ import { defineCommand as defineCommand147 } from "citty";
40191
40982
  registerSchema({
40192
40983
  command: "images.pinterest",
40193
40984
  description: "Pinterest image search via ScrapeCreators. Reference-grade real-world photography, product styling, interiors, fashion, food, and aesthetic mood boards. Inspect before placing \u2014 Pinterest is unverified, trademark-bearing web content.",
@@ -40207,7 +40998,7 @@ registerSchema({
40207
40998
  }
40208
40999
  }
40209
41000
  });
40210
- var pinterestCommand = defineCommand146({
41001
+ var pinterestCommand = defineCommand147({
40211
41002
  meta: {
40212
41003
  name: "pinterest",
40213
41004
  description: "Pinterest image search via ScrapeCreators ($0.00188/request). Best for photo-realistic reference imagery \u2014 lifestyle, interiors, fashion, food, product styling, and mood boards to brief AI generation against. \u26A0 Unverified, trademark-bearing web content \u2014 inspect and respect rights before placing on a customer page. Browse first; auto-ingest only the pins you commit to.\n\nExamples:\n baker images pinterest 'scandinavian living room'\n baker images pinterest 'minimalist skincare product photography' --limit 20\n baker images pinterest 'cozy coffee shop interior' --auto-ingest 2 --context 'Mood reference for hero photography'"
@@ -40264,7 +41055,7 @@ var pinterestCommand = defineCommand146({
40264
41055
  });
40265
41056
 
40266
41057
  // src/commands/images/screenshot.ts
40267
- import { defineCommand as defineCommand147 } from "citty";
41058
+ import { defineCommand as defineCommand148 } from "citty";
40268
41059
  registerSchema({
40269
41060
  command: "images.screenshot",
40270
41061
  description: "Capture a website screenshot via ScreenshotOne. Auto-ingests on success.",
@@ -40283,7 +41074,7 @@ registerSchema({
40283
41074
  full: { type: "boolean", description: "Include full metadata", required: false, default: false }
40284
41075
  }
40285
41076
  });
40286
- var screenshotCommand = defineCommand147({
41077
+ var screenshotCommand = defineCommand148({
40287
41078
  meta: {
40288
41079
  name: "screenshot",
40289
41080
  description: "Screenshot a URL via ScreenshotOne. $0.009/capture. Auto-ingests to library.\n\nExample: baker images screenshot https://stripe.com --full-page"
@@ -40347,7 +41138,7 @@ var screenshotCommand = defineCommand147({
40347
41138
  });
40348
41139
 
40349
41140
  // src/commands/images/search.ts
40350
- import { defineCommand as defineCommand148 } from "citty";
41141
+ import { defineCommand as defineCommand149 } from "citty";
40351
41142
  registerSchema({
40352
41143
  command: "images.search",
40353
41144
  description: "Search images by text query. Only returns ready images.",
@@ -40363,7 +41154,7 @@ registerSchema({
40363
41154
  tags: { type: "string", description: "Comma-separated tags to filter by", required: false }
40364
41155
  }
40365
41156
  });
40366
- var searchCommand = defineCommand148({
41157
+ var searchCommand = defineCommand149({
40367
41158
  meta: {
40368
41159
  name: "search",
40369
41160
  description: "Semantic search images by text query. Uses hybrid BM25 + vector + reranking. Example: baker images search 'hero banner' --aspect-ratio 16:9 --tags logo"
@@ -40423,7 +41214,7 @@ var searchCommand = defineCommand148({
40423
41214
  });
40424
41215
 
40425
41216
  // src/commands/images/sticker.ts
40426
- import { defineCommand as defineCommand149 } from "citty";
41217
+ import { defineCommand as defineCommand150 } from "citty";
40427
41218
  registerSchema({
40428
41219
  command: "images.sticker",
40429
41220
  description: "Search Giphy stickers \u2014 transparent-background overlays for ad creative.",
@@ -40455,7 +41246,7 @@ registerSchema({
40455
41246
  }
40456
41247
  }
40457
41248
  });
40458
- var stickerCommand = defineCommand149({
41249
+ var stickerCommand = defineCommand150({
40459
41250
  meta: {
40460
41251
  name: "sticker",
40461
41252
  description: "Search Giphy's sticker corpus \u2014 transparent-background WebPs / GIFs ideal for overlaying on ad creative (Meta, TikTok, Stories). Same Giphy free API as `baker images gif`; results carry WebP + GIF + MP4 URLs in providerMeta.\n\nExample: baker images sticker 'thumbs up' --limit 10\nExample: baker images sticker celebration --rating g --auto-ingest 3\nExample: baker images sticker --trending --limit 25"
@@ -40502,7 +41293,7 @@ var stickerCommand = defineCommand149({
40502
41293
  });
40503
41294
 
40504
41295
  // src/commands/images/stock.ts
40505
- import { defineCommand as defineCommand150 } from "citty";
41296
+ import { defineCommand as defineCommand151 } from "citty";
40506
41297
  var STOCK_ERROR_FIX = {
40507
41298
  action: "use_different_resource",
40508
41299
  explanation: "Switch provider instead of retrying stock search. Stock search is one of several image sources. Run `baker images find <query> --sources library,pinterest,google` (`--sources` is required \u2014 `find` alone searches the library only) or `baker studio generate` to make the asset. Never sleep-and-retry this command \u2014 the CLI already backs off on rate limits. If no image can be sourced, continue the rest of the task with a placeholder rather than aborting it."
@@ -40579,7 +41370,7 @@ function buildStockRequest(query, args) {
40579
41370
  if (args.context) body.descriptionContext = args.context;
40580
41371
  return body;
40581
41372
  }
40582
- var stockCommand = defineCommand150({
41373
+ var stockCommand = defineCommand151({
40583
41374
  meta: {
40584
41375
  name: "stock",
40585
41376
  description: "Stock search via Magnific \u2014 Freepik's developer API (~250M assets: photos, vectors, illustrations, icons, PSDs). $0.002/req. With --auto-ingest, ingested hits return Baker-owned URLs.\n\nExamples:\n baker images stock 'minimalist office'\n baker images stock 'flat office workers' --type vector\n baker images stock 'hero photo of a kitchen' --type photo --orientation landscape --ai exclude\n baker images stock 'brand pattern' --color '#0a0a0a' --license freemium --auto-ingest 2"
@@ -40652,7 +41443,7 @@ var stockCommand = defineCommand150({
40652
41443
  });
40653
41444
 
40654
41445
  // src/lib/tags-command.ts
40655
- import { defineCommand as defineCommand151 } from "citty";
41446
+ import { defineCommand as defineCommand152 } from "citty";
40656
41447
  function makeTagsCommand(command, label, endpoint) {
40657
41448
  registerSchema({
40658
41449
  command: `${command}.tags`,
@@ -40661,7 +41452,7 @@ function makeTagsCommand(command, label, endpoint) {
40661
41452
  output: { type: "string", description: "Output format: md|json", required: false, default: "md" }
40662
41453
  }
40663
41454
  });
40664
- return defineCommand151({
41455
+ return defineCommand152({
40665
41456
  meta: {
40666
41457
  name: "tags",
40667
41458
  description: `List the available ${label} tag names (defaults + company custom tags). Use before filtering with --tags. Example: baker ${command} tags`
@@ -40697,7 +41488,7 @@ function makeTagsCommand(command, label, endpoint) {
40697
41488
  var tagsCommand3 = makeTagsCommand("images", "image", "/api/images/tags");
40698
41489
 
40699
41490
  // src/commands/images/upload.ts
40700
- import { defineCommand as defineCommand152 } from "citty";
41491
+ import { defineCommand as defineCommand153 } from "citty";
40701
41492
  registerSchema({
40702
41493
  command: "images.upload",
40703
41494
  description: "Upload an image to the library \u2014 local file path or remote http(s) URL.",
@@ -40735,7 +41526,7 @@ registerSchema({
40735
41526
  function isRemoteUrl2(value) {
40736
41527
  return /^https?:\/\//i.test(value);
40737
41528
  }
40738
- var uploadCommand = defineCommand152({
41529
+ var uploadCommand = defineCommand153({
40739
41530
  meta: {
40740
41531
  name: "upload",
40741
41532
  description: "Upload an image to the library \u2014 accepts a local file path OR a remote http(s) URL.\n\nLocal: reads bytes, sends to /api/images/upload, content-type auto-detected from extension.\nRemote: dispatches to /api/images/ingest with hash-dedup on bytes + externalId.\n\nExamples:\n baker images upload ./logo.png --source uploaded\n baker images upload ./cert.png --context 'ISO 27001 badge \u2014 enterprise tier'\n baker images upload https://acme.com/hero.png --source firecrawl --context 'Acme competitor pricing hero'"
@@ -40828,7 +41619,7 @@ async function uploadLocal(target, args) {
40828
41619
  }
40829
41620
 
40830
41621
  // src/commands/images/upscale.ts
40831
- import { defineCommand as defineCommand153 } from "citty";
41622
+ import { defineCommand as defineCommand154 } from "citty";
40832
41623
  registerSchema({
40833
41624
  command: "images.upscale",
40834
41625
  description: "Upscale a library image via the backend (Replicate, cost-tracked). Waits for completion by default. The image must be status 'ready' and raster (not SVG/AVIF).",
@@ -40843,7 +41634,7 @@ registerSchema({
40843
41634
  }
40844
41635
  });
40845
41636
  var POLL_INTERVAL_MS4 = 1500;
40846
- var upscaleCommand = defineCommand153({
41637
+ var upscaleCommand = defineCommand154({
40847
41638
  meta: {
40848
41639
  name: "upscale",
40849
41640
  description: "Upscale a library image via the Convex backend (Replicate, cost-tracked at $0.05/image). Waits for completion by default.\n\nExample: baker images upscale j571abc123def\nExample: baker images upscale j571abc123def --max-wait 0 # fire-and-forget"
@@ -40898,7 +41689,7 @@ var upscaleCommand = defineCommand153({
40898
41689
  });
40899
41690
 
40900
41691
  // src/commands/images/use.ts
40901
- import { defineCommand as defineCommand154 } from "citty";
41692
+ import { defineCommand as defineCommand155 } from "citty";
40902
41693
  registerSchema({
40903
41694
  command: "images.use",
40904
41695
  description: "Ingest a URL and wait for the library record to be ready.",
@@ -40927,7 +41718,7 @@ function emitReady(ingestResult, doc, args) {
40927
41718
  args.full === true
40928
41719
  );
40929
41720
  }
40930
- var useCommand = defineCommand154({
41721
+ var useCommand = defineCommand155({
40931
41722
  meta: {
40932
41723
  name: "use",
40933
41724
  description: "Sugar over `ingest`: download \u2192 store \u2192 wait until describe + embed complete \u2192 return ready library record.\n\nExample: baker images use https://cdn.example.com/hero.png --source uploaded"
@@ -40976,7 +41767,7 @@ var useCommand = defineCommand154({
40976
41767
  });
40977
41768
 
40978
41769
  // src/commands/images/index.ts
40979
- var imagesCommand = defineCommand155({
41770
+ var imagesCommand = defineCommand156({
40980
41771
  meta: {
40981
41772
  name: "images",
40982
41773
  description: `Find, source, and normalize images. Subcommands route by provider so cost + license are explicit.
@@ -41054,12 +41845,12 @@ Full guide: __tooling__/docs/tools/baker/images.md`
41054
41845
  });
41055
41846
 
41056
41847
  // src/commands/landing/index.ts
41057
- import { defineCommand as defineCommand166 } from "citty";
41848
+ import { defineCommand as defineCommand168 } from "citty";
41058
41849
 
41059
41850
  // src/commands/landing/critique.ts
41060
41851
  import { readdir as readdir9, stat as stat6 } from "fs/promises";
41061
- import path29 from "path";
41062
- import { defineCommand as defineCommand156 } from "citty";
41852
+ import path30 from "path";
41853
+ import { defineCommand as defineCommand157 } from "citty";
41063
41854
 
41064
41855
  // src/engine/landing/lib/constants.ts
41065
41856
  var OVERUSED_FONTS = /* @__PURE__ */ new Set([
@@ -42098,13 +42889,13 @@ function describeCounts(findings) {
42098
42889
  }
42099
42890
 
42100
42891
  // src/engine/landing/lib/referenceStore.ts
42101
- import { mkdir as mkdir8, readFile as readFile21, writeFile as writeFile12 } from "fs/promises";
42102
- import path26 from "path";
42892
+ import { mkdir as mkdir8, readFile as readFile22, writeFile as writeFile12 } from "fs/promises";
42893
+ import path27 from "path";
42103
42894
  var REFERENCES_FILE = ".cache/inspiration-refs.json";
42104
42895
  var REFERENCE_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
42105
42896
  async function readReferences(projectRoot) {
42106
42897
  try {
42107
- const raw = await readFile21(path26.join(projectRoot, REFERENCES_FILE), "utf8");
42898
+ const raw = await readFile22(path27.join(projectRoot, REFERENCES_FILE), "utf8");
42108
42899
  const parsed = JSON.parse(raw);
42109
42900
  if (!Array.isArray(parsed)) return [];
42110
42901
  const cutoff = Date.now() - REFERENCE_TTL_MS;
@@ -42128,8 +42919,8 @@ async function recordReferences(projectRoot, references) {
42128
42919
  const existing = await readReferences(projectRoot);
42129
42920
  const replaced = new Set(references.map((reference) => reference.sectionId));
42130
42921
  const merged = [...existing.filter((entry) => !replaced.has(entry.sectionId)), ...references];
42131
- const file = path26.join(projectRoot, REFERENCES_FILE);
42132
- await mkdir8(path26.dirname(file), { recursive: true });
42922
+ const file = path27.join(projectRoot, REFERENCES_FILE);
42923
+ await mkdir8(path27.dirname(file), { recursive: true });
42133
42924
  await writeFile12(file, `${JSON.stringify(merged, null, 2)}
42134
42925
  `);
42135
42926
  return true;
@@ -42140,13 +42931,13 @@ async function recordReferences(projectRoot, references) {
42140
42931
 
42141
42932
  // src/commands/landing/snapshot.ts
42142
42933
  import { mkdir as mkdir9, rename as rename2, writeFile as writeFile13 } from "fs/promises";
42143
- import path27 from "path";
42934
+ import path28 from "path";
42144
42935
  var CRITIC_VERSION = "2";
42145
42936
  function critiqueCacheDir(projectRoot) {
42146
- return path27.join(projectRoot, ".cache", "landing-critique");
42937
+ return path28.join(projectRoot, ".cache", "landing-critique");
42147
42938
  }
42148
42939
  function snapshotPath(projectRoot, slug) {
42149
- return path27.join(critiqueCacheDir(projectRoot), `${slug}.json`);
42940
+ return path28.join(critiqueCacheDir(projectRoot), `${slug}.json`);
42150
42941
  }
42151
42942
  async function writeCritiqueSnapshot(projectRoot, snapshot) {
42152
42943
  await mkdir9(critiqueCacheDir(projectRoot), { recursive: true });
@@ -42158,30 +42949,26 @@ async function writeCritiqueSnapshot(projectRoot, snapshot) {
42158
42949
  }
42159
42950
 
42160
42951
  // src/commands/landing/source-version.ts
42161
- import { readdir as readdir8, readFile as readFile22, stat as stat5 } from "fs/promises";
42162
- import path28 from "path";
42163
- async function landingSourceRelPaths(landingDir) {
42164
- const rel = [];
42165
- if (await isFile(path28.join(landingDir, "index.astro"))) rel.push("index.astro");
42166
- const componentsDir = path28.join(landingDir, "_components");
42167
- for (const abs of await walkAstro(componentsDir)) {
42168
- rel.push(path28.relative(landingDir, abs).split(path28.sep).join("/"));
42169
- }
42170
- return rel.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
42952
+ import { readFile as readFile23 } from "fs/promises";
42953
+ import path29 from "path";
42954
+ var CRITIQUED_ROOTS = ["src/pages/", "src/components/"];
42955
+ async function landingSourceRelPaths(root, slug) {
42956
+ const files = await landingGraphFiles(root, slug);
42957
+ return [...files].filter((rel) => rel.endsWith(".astro") && CRITIQUED_ROOTS.some((r) => rel.startsWith(r))).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
42171
42958
  }
42172
- async function readLandingSources(landingDir) {
42173
- const rel = await landingSourceRelPaths(landingDir);
42959
+ async function readLandingSources(root, slug) {
42960
+ const rel = await landingSourceRelPaths(root, slug);
42174
42961
  const out = [];
42175
- for (const r of rel) out.push({ path: r, text: await readFile22(path28.join(landingDir, r), "utf8") });
42962
+ for (const r of rel) out.push({ path: r, text: await readFile23(path29.join(root, r), "utf8") });
42176
42963
  return out;
42177
42964
  }
42178
- async function computeLandingSourceSha(landingDir) {
42179
- const rel = await landingSourceRelPaths(landingDir);
42965
+ async function computeLandingSourceSha(root, slug) {
42966
+ const rel = await landingSourceRelPaths(root, slug);
42180
42967
  const parts = [];
42181
42968
  for (const r of rel) {
42182
42969
  let bytes;
42183
42970
  try {
42184
- bytes = await readFile22(path28.join(landingDir, r));
42971
+ bytes = await readFile23(path29.join(root, r));
42185
42972
  } catch {
42186
42973
  bytes = Buffer.alloc(0);
42187
42974
  }
@@ -42189,28 +42976,6 @@ async function computeLandingSourceSha(landingDir) {
42189
42976
  }
42190
42977
  return sha256Hex(Buffer.concat(parts));
42191
42978
  }
42192
- async function isFile(p) {
42193
- try {
42194
- return (await stat5(p)).isFile();
42195
- } catch {
42196
- return false;
42197
- }
42198
- }
42199
- async function walkAstro(dir) {
42200
- let entries;
42201
- try {
42202
- entries = await readdir8(dir, { withFileTypes: true });
42203
- } catch {
42204
- return [];
42205
- }
42206
- const out = [];
42207
- for (const entry of entries) {
42208
- const abs = path28.join(dir, entry.name);
42209
- if (entry.isDirectory()) out.push(...await walkAstro(abs));
42210
- else if (entry.isFile() && entry.name.endsWith(".astro")) out.push(abs);
42211
- }
42212
- return out;
42213
- }
42214
42979
 
42215
42980
  // src/commands/landing/critique.ts
42216
42981
  registerSchema({
@@ -42235,14 +43000,14 @@ function parseCritiqueSlugs(args) {
42235
43000
  const all = [args.slug, ...rest].filter((s) => typeof s === "string" && s.length > 0);
42236
43001
  return [...new Set(all.map(String))];
42237
43002
  }
42238
- function fail5(code, message, fix) {
43003
+ function fail6(code, message, fix) {
42239
43004
  process.stderr.write(
42240
43005
  `${JSON.stringify({ ok: false, error: { code, message, ...fix ? { fix } : {} } }, null, 2)}
42241
43006
  `
42242
43007
  );
42243
43008
  process.exit(2);
42244
43009
  }
42245
- var critiqueCommand2 = defineCommand156({
43010
+ var critiqueCommand2 = defineCommand157({
42246
43011
  meta: {
42247
43012
  name: "critique",
42248
43013
  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."
@@ -42260,14 +43025,14 @@ var critiqueCommand2 = defineCommand156({
42260
43025
  const projectRoot = process.cwd();
42261
43026
  for (const slug of slugs) {
42262
43027
  if (!SLUG_RE.test(slug) || slug.includes("..")) {
42263
- fail5(
43028
+ fail6(
42264
43029
  "INVALID_SLUG",
42265
43030
  `"${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).`,
42266
43031
  { availableSlugs: await listLandingSlugs(projectRoot) }
42267
43032
  );
42268
43033
  }
42269
- if (!await isDir(path29.resolve(projectRoot, "src", "pages", slug))) {
42270
- fail5("NOT_FOUND", `No landing at src/pages/${slug}/`, {
43034
+ if (!await isDir(path30.resolve(projectRoot, "src", "pages", slug))) {
43035
+ fail6("NOT_FOUND", `No landing at src/pages/${slug}/`, {
42271
43036
  availableSlugs: await listLandingSlugs(projectRoot)
42272
43037
  });
42273
43038
  }
@@ -42305,8 +43070,10 @@ var critiqueCommand2 = defineCommand156({
42305
43070
  }
42306
43071
  });
42307
43072
  async function critiqueOne(projectRoot, slug, brand, references) {
42308
- const landingDir = path29.resolve(projectRoot, "src", "pages", slug);
42309
- const [sources, sourceSha] = await Promise.all([readLandingSources(landingDir), computeLandingSourceSha(landingDir)]);
43073
+ const [sources, sourceSha] = await Promise.all([
43074
+ readLandingSources(projectRoot, slug),
43075
+ computeLandingSourceSha(projectRoot, slug)
43076
+ ]);
42310
43077
  const report = critiqueLanding({ slug, sources, brand, references });
42311
43078
  let snapshotFailed = false;
42312
43079
  try {
@@ -42325,7 +43092,7 @@ async function critiqueOne(projectRoot, slug, brand, references) {
42325
43092
  }
42326
43093
  async function listLandingSlugs(projectRoot) {
42327
43094
  try {
42328
- const entries = await readdir9(path29.join(projectRoot, "src", "pages"), { withFileTypes: true });
43095
+ const entries = await readdir9(path30.join(projectRoot, "src", "pages"), { withFileTypes: true });
42329
43096
  return entries.filter((e) => e.isDirectory() && !e.name.startsWith("_") && !e.name.startsWith(".")).map((e) => e.name).sort();
42330
43097
  } catch {
42331
43098
  return [];
@@ -42351,10 +43118,10 @@ async function isDir(p) {
42351
43118
  }
42352
43119
 
42353
43120
  // src/commands/landing/inspiration/index.ts
42354
- import { defineCommand as defineCommand165 } from "citty";
43121
+ import { defineCommand as defineCommand166 } from "citty";
42355
43122
 
42356
43123
  // src/commands/landing/inspiration/add.ts
42357
- import { defineCommand as defineCommand157 } from "citty";
43124
+ import { defineCommand as defineCommand158 } from "citty";
42358
43125
 
42359
43126
  // src/commands/landing/inspiration/shared.ts
42360
43127
  var INSPIRATION_HINTS = {
@@ -42436,7 +43203,7 @@ registerSchema({
42436
43203
  note: { type: "string", description: "Why this page is worth keeping", required: false }
42437
43204
  }
42438
43205
  });
42439
- var addCommand = defineCommand157({
43206
+ var addCommand = defineCommand158({
42440
43207
  meta: {
42441
43208
  name: "add",
42442
43209
  description: "Add someone else's landing page to the reference library. Example: baker landing inspiration add https://linear.app --note 'the client likes this density'"
@@ -42477,8 +43244,8 @@ var addCommand = defineCommand157({
42477
43244
 
42478
43245
  // src/commands/landing/inspiration/code.ts
42479
43246
  import { mkdir as mkdir10, writeFile as writeFile14 } from "fs/promises";
42480
- import path30 from "path";
42481
- import { defineCommand as defineCommand158 } from "citty";
43247
+ import path31 from "path";
43248
+ import { defineCommand as defineCommand159 } from "citty";
42482
43249
  registerSchema({
42483
43250
  command: "landing.inspiration.code",
42484
43251
  description: "Write a reference section's standalone HTML+CSS to .baker/inspiration/<id>/ so you can read how it is built. Reference only \u2014 the structure is the lesson, the words are not yours to reuse. Consulting a section records it, and `baker landing critique` blocks a publish that ships its copy verbatim.",
@@ -42487,7 +43254,7 @@ registerSchema({
42487
43254
  full: { type: "boolean", description: "Print the markup inline as well as writing it", required: false }
42488
43255
  }
42489
43256
  });
42490
- var codeCommand = defineCommand158({
43257
+ var codeCommand = defineCommand159({
42491
43258
  meta: {
42492
43259
  name: "code",
42493
43260
  description: "Write one reference section's standalone markup to disk. Example: baker landing inspiration code k57abc\u2026 \u2014 read it for structure, then build your own."
@@ -42500,9 +43267,9 @@ var codeCommand = defineCommand158({
42500
43267
  try {
42501
43268
  const id = args.id;
42502
43269
  const data = await apiGet("/api/landing-inspiration/section-code", { id });
42503
- const dir = path30.join(process.cwd(), ".baker", "inspiration", id);
43270
+ const dir = path31.join(process.cwd(), ".baker", "inspiration", id);
42504
43271
  await mkdir10(dir, { recursive: true });
42505
- const file = path30.join(dir, "section.html");
43272
+ const file = path31.join(dir, "section.html");
42506
43273
  await writeFile14(file, data.html);
42507
43274
  const recorded = await recordReference(process.cwd(), {
42508
43275
  sectionId: id,
@@ -42523,7 +43290,7 @@ var codeCommand = defineCommand158({
42523
43290
  ok: true,
42524
43291
  data: {
42525
43292
  id,
42526
- file: path30.relative(process.cwd(), file),
43293
+ file: path31.relative(process.cwd(), file),
42527
43294
  bytes: data.html.length,
42528
43295
  fidelity: data.fidelity,
42529
43296
  reproduction_notes: data.reproductionNotes,
@@ -42544,7 +43311,7 @@ var codeCommand = defineCommand158({
42544
43311
  });
42545
43312
 
42546
43313
  // src/commands/landing/inspiration/favorites.ts
42547
- import { defineCommand as defineCommand159 } from "citty";
43314
+ import { defineCommand as defineCommand160 } from "citty";
42548
43315
  registerSchema({
42549
43316
  command: "landing.inspiration.favorites",
42550
43317
  description: "List the reference sections this company has saved. This is what `search` looks at by default, so it is the client's own taste profile \u2014 read it before proposing a direction.",
@@ -42558,7 +43325,7 @@ registerSchema({
42558
43325
  }
42559
43326
  }
42560
43327
  });
42561
- var favoritesCommand = defineCommand159({
43328
+ var favoritesCommand = defineCommand160({
42562
43329
  meta: {
42563
43330
  name: "favorites",
42564
43331
  description: "List this company's saved reference sections. Example: baker landing inspiration favorites --type hero,pricing"
@@ -42638,7 +43405,7 @@ registerSchema({
42638
43405
  note: { type: "string", description: "Why this is worth keeping", required: false }
42639
43406
  }
42640
43407
  });
42641
- var favoriteCommand = defineCommand159({
43408
+ var favoriteCommand = defineCommand160({
42642
43409
  meta: {
42643
43410
  name: "favorite",
42644
43411
  description: "Save a reference section to this company. Example: baker landing inspiration favorite k57abc\u2026"
@@ -42676,7 +43443,7 @@ registerSchema({
42676
43443
  page: { type: "boolean", description: "Treat the id as a page rather than a section", required: false }
42677
43444
  }
42678
43445
  });
42679
- var unfavoriteCommand = defineCommand159({
43446
+ var unfavoriteCommand = defineCommand160({
42680
43447
  meta: {
42681
43448
  name: "unfavorite",
42682
43449
  description: "Remove a reference section from this company's saved set. Example: baker landing inspiration unfavorite k57abc\u2026"
@@ -42698,7 +43465,7 @@ var unfavoriteCommand = defineCommand159({
42698
43465
  });
42699
43466
 
42700
43467
  // src/commands/landing/inspiration/page.ts
42701
- import { defineCommand as defineCommand160 } from "citty";
43468
+ import { defineCommand as defineCommand161 } from "citty";
42702
43469
  registerSchema({
42703
43470
  command: "landing.inspiration.page",
42704
43471
  description: "Show a whole reference page as a sequence: every section top to bottom with its type and the idea behind it. This is the view to use when the question is how a good page is ORDERED rather than what one section looks like.",
@@ -42715,7 +43482,7 @@ registerSchema({
42715
43482
  }
42716
43483
  }
42717
43484
  });
42718
- var pageCommand2 = defineCommand160({
43485
+ var pageCommand2 = defineCommand161({
42719
43486
  meta: {
42720
43487
  name: "page",
42721
43488
  description: "Show how a reference page sequences its sections. Example: baker landing inspiration page j91xyz\u2026 \u2014 the blueprint, not the pixels."
@@ -42778,9 +43545,9 @@ var pageCommand2 = defineCommand160({
42778
43545
  });
42779
43546
 
42780
43547
  // src/commands/landing/inspiration/scrape.ts
42781
- import { readFile as readFile23 } from "fs/promises";
42782
- import path34 from "path";
42783
- import { defineCommand as defineCommand161 } from "citty";
43548
+ import { readFile as readFile24 } from "fs/promises";
43549
+ import path35 from "path";
43550
+ import { defineCommand as defineCommand162 } from "citty";
42784
43551
 
42785
43552
  // src/engine/landing/lib/capturedReferences.ts
42786
43553
  var MAX_STRINGS_PER_SECTION = 40;
@@ -42972,7 +43739,7 @@ function classifyCaptureFailure(error) {
42972
43739
 
42973
43740
  // src/engine/landing-library/run.ts
42974
43741
  import { mkdir as mkdir11, writeFile as writeFile16 } from "fs/promises";
42975
- import path32 from "path";
43742
+ import path33 from "path";
42976
43743
 
42977
43744
  // ../proxy/src/preflight.ts
42978
43745
  import http from "http";
@@ -44388,9 +45155,9 @@ async function renderBundleToPng(browser, html, viewportWidth, options = {}) {
44388
45155
 
44389
45156
  // src/engine/landing-library/report.ts
44390
45157
  import { writeFile as writeFile15 } from "fs/promises";
44391
- import path31 from "path";
45158
+ import path32 from "path";
44392
45159
  async function writeCaptureReport(manifest, outDir) {
44393
- const file = path31.join(outDir, "report.html");
45160
+ const file = path32.join(outDir, "report.html");
44394
45161
  await writeFile15(file, renderReport(manifest));
44395
45162
  return file;
44396
45163
  }
@@ -44569,32 +45336,32 @@ async function reproducePage(args) {
44569
45336
  const { browser, page, outDir, pageUrl, livePageShot } = args;
44570
45337
  const built = await buildSectionBundle(page, "body", pageUrl).catch(() => null);
44571
45338
  if (!built) return { bundle: null, fidelity: null };
44572
- await writeFile16(path32.join(outDir, "page.html"), built.html);
45339
+ await writeFile16(path33.join(outDir, "page.html"), built.html);
44573
45340
  const rendered = await renderBundleToPng(browser, built.html, DESKTOP_VIEWPORT.width, {
44574
45341
  wholePage: true,
44575
45342
  timeoutMs: 6e4
44576
45343
  });
44577
45344
  if (!rendered || !livePageShot) return { bundle: "page.html", fidelity: null };
44578
- await writeFile16(path32.join(outDir, "page-rendered.png"), rendered);
45345
+ await writeFile16(path33.join(outDir, "page-rendered.png"), rendered);
44579
45346
  const { score, note } = await scoreFidelity(livePageShot, rendered);
44580
45347
  return { bundle: "page.html", fidelity: score, ...note ? { fidelityNote: note } : {} };
44581
45348
  }
44582
45349
  async function captureOneSection(args) {
44583
45350
  const { browser, page, candidate, sectionsDir, outDir, pageUrl, withCode } = args;
44584
- const dir = path32.join(sectionsDir, String(candidate.index).padStart(2, "0"));
45351
+ const dir = path33.join(sectionsDir, String(candidate.index).padStart(2, "0"));
44585
45352
  await mkdir11(dir, { recursive: true });
44586
45353
  const desktop = await captureSection(page, candidate);
44587
- if (desktop) await writeFile16(path32.join(dir, "desktop.png"), desktop);
45354
+ if (desktop) await writeFile16(path33.join(dir, "desktop.png"), desktop);
44588
45355
  const visualHash = desktop ? await perceptualHash(desktop) : null;
44589
45356
  const motion = await collectMotion(page, candidate.selector);
44590
45357
  const built = withCode ? await buildSectionBundle(page, candidate.selector, pageUrl) : null;
44591
45358
  let fidelity = null;
44592
45359
  let fidelityNote;
44593
45360
  if (built) {
44594
- await writeFile16(path32.join(dir, "section.html"), built.html);
45361
+ await writeFile16(path33.join(dir, "section.html"), built.html);
44595
45362
  const rendered = await renderBundleToPng(browser, built.html, DESKTOP_VIEWPORT.width);
44596
45363
  if (rendered && desktop) {
44597
- await writeFile16(path32.join(dir, "section-rendered.png"), rendered);
45364
+ await writeFile16(path33.join(dir, "section-rendered.png"), rendered);
44598
45365
  const result = await scoreFidelity(desktop, rendered);
44599
45366
  fidelity = result.score;
44600
45367
  fidelityNote = result.note;
@@ -44602,9 +45369,9 @@ async function captureOneSection(args) {
44602
45369
  }
44603
45370
  return {
44604
45371
  ...candidate,
44605
- desktopShot: desktop ? path32.relative(outDir, path32.join(dir, "desktop.png")) : null,
45372
+ desktopShot: desktop ? path33.relative(outDir, path33.join(dir, "desktop.png")) : null,
44606
45373
  mobileShot: null,
44607
- bundle: built ? path32.relative(outDir, path32.join(dir, "section.html")) : null,
45374
+ bundle: built ? path33.relative(outDir, path33.join(dir, "section.html")) : null,
44608
45375
  fidelity,
44609
45376
  ...fidelityNote ? { fidelityNote } : {},
44610
45377
  ...built ? { cssStats: built.stats } : {},
@@ -44623,9 +45390,9 @@ async function captureMobileShots(args) {
44623
45390
  for (const section of sections) {
44624
45391
  const shot = await captureSectionOnMobile(mobile.page, section);
44625
45392
  if (!shot) continue;
44626
- const file = path32.join(sectionsDir, String(section.index).padStart(2, "0"), "mobile.png");
45393
+ const file = path33.join(sectionsDir, String(section.index).padStart(2, "0"), "mobile.png");
44627
45394
  await writeFile16(file, shot);
44628
- section.mobileShot = path32.relative(outDir, file);
45395
+ section.mobileShot = path33.relative(outDir, file);
44629
45396
  }
44630
45397
  } finally {
44631
45398
  await mobile.context.close();
@@ -44640,10 +45407,10 @@ async function captureMotionTakes(args) {
44640
45407
  const filmOne = async (section) => {
44641
45408
  const take = await captureMotionTake(browser, pageUrl, section.selector).catch(() => null);
44642
45409
  if (!take) return;
44643
- const dir = path32.join(sectionsDir, String(section.index).padStart(2, "0"));
44644
- const file = path32.join(dir, "motion-filmstrip.png");
45410
+ const dir = path33.join(sectionsDir, String(section.index).padStart(2, "0"));
45411
+ const file = path33.join(dir, "motion-filmstrip.png");
44645
45412
  await writeFile16(file, take.filmstrip);
44646
- section.motionFilmstrip = path32.relative(outDir, file);
45413
+ section.motionFilmstrip = path33.relative(outDir, file);
44647
45414
  log(` [${section.index}] ${section.motion.summary}`);
44648
45415
  };
44649
45416
  const queue = [...moving];
@@ -44700,7 +45467,7 @@ async function captureAlternateViews(args) {
44700
45467
  async function reproduceWholePage(args) {
44701
45468
  const { browser, page, outDir, pageUrl, withCode, log } = args;
44702
45469
  const fullPage = await page.screenshot({ type: "png", fullPage: true }).catch(() => null);
44703
- if (fullPage) await writeFile16(path32.join(outDir, "full-page.png"), fullPage);
45470
+ if (fullPage) await writeFile16(path33.join(outDir, "full-page.png"), fullPage);
44704
45471
  if (!withCode) return { bundle: null, fidelity: null };
44705
45472
  const reproduction = await reproducePage({ browser, page, outDir, pageUrl, livePageShot: fullPage });
44706
45473
  log(`page reproduction: ${reproduction.fidelity === null ? "unavailable" : reproduction.fidelity.toFixed(2)}`);
@@ -44771,7 +45538,7 @@ async function openViaLadder(args) {
44771
45538
  async function scrapeLanding(options) {
44772
45539
  const timeoutMs = options.timeoutMs ?? 45e3;
44773
45540
  const log = options.onProgress ?? (() => void 0);
44774
- const sectionsDir = path32.join(options.outDir, "sections");
45541
+ const sectionsDir = path33.join(options.outDir, "sections");
44775
45542
  const nonPublic = refuseNonPublicUrl(options.url);
44776
45543
  if (nonPublic) {
44777
45544
  throw new BlockedPageError({
@@ -44833,7 +45600,7 @@ async function scrapeLanding(options) {
44833
45600
  security: prepared.security,
44834
45601
  captureTier: tier
44835
45602
  };
44836
- await writeFile16(path32.join(options.outDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
45603
+ await writeFile16(path33.join(options.outDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
44837
45604
  `);
44838
45605
  if (options.report !== false) {
44839
45606
  const reportPath = await writeCaptureReport(manifest, options.outDir);
@@ -44848,28 +45615,28 @@ async function scrapeLanding(options) {
44848
45615
 
44849
45616
  // src/commands/landing/inspiration/captureOut.ts
44850
45617
  import { existsSync as existsSync9 } from "fs";
44851
- import path33 from "path";
45618
+ import path34 from "path";
44852
45619
  var SCRATCH_DIR = ".baker";
44853
45620
  function isWithin(parent, target) {
44854
- const relative = path33.relative(parent, target);
44855
- return relative === "" || !relative.startsWith("..") && !path33.isAbsolute(relative);
45621
+ const relative = path34.relative(parent, target);
45622
+ return relative === "" || !relative.startsWith("..") && !path34.isAbsolute(relative);
44856
45623
  }
44857
45624
  function findRepoRoot(from) {
44858
- let dir = path33.resolve(from);
45625
+ let dir = path34.resolve(from);
44859
45626
  for (; ; ) {
44860
- if (existsSync9(path33.join(dir, ".git"))) return dir;
44861
- const parent = path33.dirname(dir);
45627
+ if (existsSync9(path34.join(dir, ".git"))) return dir;
45628
+ const parent = path34.dirname(dir);
44862
45629
  if (parent === dir) return null;
44863
45630
  dir = parent;
44864
45631
  }
44865
45632
  }
44866
45633
  function checkCaptureOut(out, options) {
44867
45634
  const { cwd, repoRoot } = options;
44868
- const resolved = path33.resolve(cwd, out);
45635
+ const resolved = path34.resolve(cwd, out);
44869
45636
  if (repoRoot === null || !isWithin(repoRoot, resolved)) return { ok: true };
44870
- const scratch = path33.join(repoRoot, SCRATCH_DIR);
45637
+ const scratch = path34.join(repoRoot, SCRATCH_DIR);
44871
45638
  if (isWithin(scratch, resolved)) return { ok: true };
44872
- const suggestion = path33.posix.join(SCRATCH_DIR, "teardowns", path33.basename(resolved) || "capture");
45639
+ const suggestion = path34.posix.join(SCRATCH_DIR, "teardowns", path34.basename(resolved) || "capture");
44873
45640
  return {
44874
45641
  ok: false,
44875
45642
  error: {
@@ -44898,7 +45665,7 @@ async function recordCapture(manifest, outDir) {
44898
45665
  for (const section of manifest.sections) {
44899
45666
  if (!section.bundle) continue;
44900
45667
  try {
44901
- const markup = await readFile23(path34.join(outDir, section.bundle), "utf8");
45668
+ const markup = await readFile24(path35.join(outDir, section.bundle), "utf8");
44902
45669
  const copyStrings = capturedCopyStrings(markup);
44903
45670
  if (copyStrings.length === 0) continue;
44904
45671
  references.push({
@@ -44959,7 +45726,7 @@ registerSchema({
44959
45726
  report: { type: "boolean", description: "Write report.html. `--no-report` to skip", required: false }
44960
45727
  }
44961
45728
  });
44962
- var scrapeCommand = defineCommand161({
45729
+ var scrapeCommand = defineCommand162({
44963
45730
  meta: {
44964
45731
  name: "scrape",
44965
45732
  description: "Capture a landing page to a directory, now. Example: baker landing inspiration scrape https://linear.app --out .baker/inspiration/linear.app"
@@ -45068,12 +45835,12 @@ var scrapeCommand = defineCommand161({
45068
45835
  });
45069
45836
 
45070
45837
  // src/commands/landing/inspiration/search.ts
45071
- import path36 from "path";
45072
- import { defineCommand as defineCommand162 } from "citty";
45838
+ import path37 from "path";
45839
+ import { defineCommand as defineCommand163 } from "citty";
45073
45840
 
45074
45841
  // src/commands/landing/inspiration/shot.ts
45075
45842
  import { mkdir as mkdir12, writeFile as writeFile17 } from "fs/promises";
45076
- import path35 from "path";
45843
+ import path36 from "path";
45077
45844
  import sharp6 from "sharp";
45078
45845
  var READABLE_SHOT = {
45079
45846
  maxWidth: 1440,
@@ -45099,9 +45866,9 @@ async function downloadReadableShot(url, file) {
45099
45866
  const response = await fetch(url);
45100
45867
  if (!response.ok) return null;
45101
45868
  const shot = await toReadableShot(Buffer.from(await response.arrayBuffer()));
45102
- await mkdir12(path35.dirname(file), { recursive: true });
45869
+ await mkdir12(path36.dirname(file), { recursive: true });
45103
45870
  await writeFile17(file, shot);
45104
- return path35.relative(process.cwd(), file);
45871
+ return path36.relative(process.cwd(), file);
45105
45872
  } catch {
45106
45873
  return null;
45107
45874
  }
@@ -45193,20 +45960,20 @@ function buildSearchBody(args) {
45193
45960
  return body;
45194
45961
  }
45195
45962
  async function downloadShots(results) {
45196
- const dir = path36.join(process.cwd(), ".baker", "inspiration");
45963
+ const dir = path37.join(process.cwd(), ".baker", "inspiration");
45197
45964
  const saved = /* @__PURE__ */ new Map();
45198
45965
  await Promise.all(
45199
45966
  results.map(async (result) => {
45200
45967
  const file = await downloadReadableShot(
45201
45968
  result.desktopShotUrl,
45202
- path36.join(dir, `${result.id}.${READABLE_SHOT.extension}`)
45969
+ path37.join(dir, `${result.id}.${READABLE_SHOT.extension}`)
45203
45970
  );
45204
45971
  if (file) saved.set(result.id, file);
45205
45972
  })
45206
45973
  );
45207
45974
  return saved;
45208
45975
  }
45209
- var searchCommand2 = defineCommand162({
45976
+ var searchCommand2 = defineCommand163({
45210
45977
  meta: {
45211
45978
  name: "search",
45212
45979
  description: "Search real landing-page sections for reference. Example: baker landing inspiration search 'dark developer hero with a terminal' --register dev-tool-minimal --scope all"
@@ -45304,7 +46071,7 @@ var searchCommand2 = defineCommand162({
45304
46071
  });
45305
46072
 
45306
46073
  // src/commands/landing/inspiration/sequences.ts
45307
- import { defineCommand as defineCommand163 } from "citty";
46074
+ import { defineCommand as defineCommand164 } from "citty";
45308
46075
  var COMPACT_TRANSITIONS = 12;
45309
46076
  var COMPACT_ENDS = 5;
45310
46077
  var MAX_PAGES = 50;
@@ -45378,7 +46145,7 @@ function sequencesHints(data, { scope, full }) {
45378
46145
  if (scope === "favorites") hints.push(...favoritesScopeHints(data.favoritesHealth, data.pagesReturned));
45379
46146
  return hints;
45380
46147
  }
45381
- var sequencesCommand = defineCommand163({
46148
+ var sequencesCommand = defineCommand164({
45382
46149
  meta: {
45383
46150
  name: "sequences",
45384
46151
  description: "What section follows what, across many real pages at once. Example: baker landing inspiration sequences 'developer tool pricing page' --scope all \u2014 the evidence for how to order a page you are about to build."
@@ -45427,8 +46194,8 @@ var sequencesCommand = defineCommand163({
45427
46194
  });
45428
46195
 
45429
46196
  // src/commands/landing/inspiration/view.ts
45430
- import path37 from "path";
45431
- import { defineCommand as defineCommand164 } from "citty";
46197
+ import path38 from "path";
46198
+ import { defineCommand as defineCommand165 } from "citty";
45432
46199
  registerSchema({
45433
46200
  command: "landing.inspiration.view",
45434
46201
  description: "Everything known about one section: composition, motion, design tokens, the copy it uses, why it works, and what must change to make it yours. Downloads the desktop and mobile screenshots plus the motion filmstrip so you can look at them.",
@@ -45441,7 +46208,7 @@ registerSchema({
45441
46208
  }
45442
46209
  }
45443
46210
  });
45444
- var viewCommand2 = defineCommand164({
46211
+ var viewCommand2 = defineCommand165({
45445
46212
  meta: {
45446
46213
  name: "view",
45447
46214
  description: "Full detail for one reference section. Example: baker landing inspiration view k57abc\u2026 \u2014 read the screenshots it saves before you build."
@@ -45460,12 +46227,12 @@ var viewCommand2 = defineCommand164({
45460
46227
  const id = args.id;
45461
46228
  const data = await apiGet("/api/landing-inspiration/section", { id });
45462
46229
  const section = data.section;
45463
- const dir = path37.join(process.cwd(), ".baker", "inspiration", id);
46230
+ const dir = path38.join(process.cwd(), ".baker", "inspiration", id);
45464
46231
  const ext = READABLE_SHOT.extension;
45465
46232
  const [desktop, mobile, filmstrip] = await Promise.all([
45466
- downloadReadableShot(section.desktopShotUrl, path37.join(dir, `desktop.${ext}`)),
45467
- downloadReadableShot(section.mobileShotUrl, path37.join(dir, `mobile.${ext}`)),
45468
- downloadReadableShot(section.motionFilmstripUrl, path37.join(dir, `motion-filmstrip.${ext}`))
46233
+ downloadReadableShot(section.desktopShotUrl, path38.join(dir, `desktop.${ext}`)),
46234
+ downloadReadableShot(section.mobileShotUrl, path38.join(dir, `mobile.${ext}`)),
46235
+ downloadReadableShot(section.motionFilmstripUrl, path38.join(dir, `motion-filmstrip.${ext}`))
45469
46236
  ]);
45470
46237
  const full = args.full;
45471
46238
  const hints = [INSPIRATION_HINTS.structureNotCopy, INSPIRATION_HINTS.adapt];
@@ -45525,7 +46292,7 @@ var viewCommand2 = defineCommand164({
45525
46292
  });
45526
46293
 
45527
46294
  // src/commands/landing/inspiration/index.ts
45528
- var inspirationCommand = defineCommand165({
46295
+ var inspirationCommand = defineCommand166({
45529
46296
  meta: {
45530
46297
  name: "inspiration",
45531
46298
  description: `Reference library of real landing-page sections \u2014 look at how good pages actually solve a problem before you design one.
@@ -45569,8 +46336,310 @@ Full guide: __tooling__/docs/tools/baker/landing.md`
45569
46336
  }
45570
46337
  });
45571
46338
 
46339
+ // src/commands/landing/variant.ts
46340
+ import { randomUUID as randomUUID2 } from "crypto";
46341
+ import { mkdir as mkdir13, readdir as readdir10, readFile as readFile25, stat as stat7, writeFile as writeFile18 } from "fs/promises";
46342
+ import path40 from "path";
46343
+ import { defineCommand as defineCommand167 } from "citty";
46344
+
46345
+ // src/commands/landing/repoint.ts
46346
+ import path39 from "path";
46347
+ var QUOTED_RELATIVE = /(['"])(\.\.?\/[^'"]*)\1/g;
46348
+ function isInside3(abs, dir) {
46349
+ return abs === dir || abs.startsWith(dir + path39.sep);
46350
+ }
46351
+ function toSpecifier(from, to) {
46352
+ const rel = path39.relative(from, to).split(path39.sep).join("/");
46353
+ return rel.startsWith(".") ? rel : `./${rel}`;
46354
+ }
46355
+ function repointSpecifier(spec, opts) {
46356
+ if (!spec.startsWith(".")) return spec;
46357
+ const abs = path39.resolve(opts.fromDir, spec);
46358
+ if (opts.forkedTargets?.has(abs)) {
46359
+ const withinControl = path39.relative(opts.controlDir, abs);
46360
+ return toSpecifier(opts.toDir, path39.resolve(opts.variantDir, withinControl));
46361
+ }
46362
+ if (!isInside3(abs, opts.controlDir)) return spec;
46363
+ return toSpecifier(opts.toDir, abs);
46364
+ }
46365
+ function repointFile(text2, opts) {
46366
+ return text2.replace(QUOTED_RELATIVE, (_match, quote, spec) => {
46367
+ return `${quote}${repointSpecifier(spec, opts)}${quote}`;
46368
+ });
46369
+ }
46370
+
46371
+ // src/commands/landing/variant.ts
46372
+ registerSchema({
46373
+ command: "landing.variant",
46374
+ description: "Create the alternative version of a landing for an A/B test. The new page shares the control's sections instead of copying them, so the only difference between the two is the one you fork \u2014 which is the only way the test measures what you think it measures. Run `baker experiment plan` FIRST: most pages cannot settle most questions.",
46375
+ args: {
46376
+ control: { type: "string", description: "The page under test, by slug", required: true },
46377
+ slug: { type: "string", description: "Slug for the alternative page (convention: <control>-b)", required: true },
46378
+ fork: {
46379
+ type: "string",
46380
+ description: "The component the variant owns its own copy of, named as it appears under _components/ (`Hero.astro`, or `hero/Card.astro`). Repeat for several. Everything else is shared with the control, so a later edit to the control reaches both arms and cannot skew the test.",
46381
+ required: false
46382
+ },
46383
+ because: {
46384
+ type: "string",
46385
+ description: "What you SAW that makes this worth building \u2014 the observation, not the change. Required here, before the page exists, because a reason written afterwards is a caption for a page rather than the reason for one.",
46386
+ required: true
46387
+ },
46388
+ change: {
46389
+ type: "string",
46390
+ description: "What is different about this version, in the client's words \u2014 \u201Cput the booking form in the hero\u201D.",
46391
+ required: true
46392
+ }
46393
+ }
46394
+ });
46395
+ var SLUG_RE2 = /^[a-z0-9][a-z0-9-]*$/;
46396
+ function fail7(code, message, fix) {
46397
+ process.stderr.write(
46398
+ `${JSON.stringify({ ok: false, error: { code, message, ...fix ? { fix } : {} } }, null, 2)}
46399
+ `
46400
+ );
46401
+ process.exit(2);
46402
+ }
46403
+ async function isDir2(p) {
46404
+ try {
46405
+ return (await stat7(p)).isDirectory();
46406
+ } catch {
46407
+ return false;
46408
+ }
46409
+ }
46410
+ async function exists(p) {
46411
+ try {
46412
+ await stat7(p);
46413
+ return true;
46414
+ } catch {
46415
+ return false;
46416
+ }
46417
+ }
46418
+ async function listLandingSlugs2(root) {
46419
+ try {
46420
+ const entries = await readdir10(path40.resolve(root, "src", "pages"), { withFileTypes: true });
46421
+ return entries.filter((e) => e.isDirectory() && !e.name.startsWith("_") && !e.name.startsWith(".")).map((e) => e.name).sort();
46422
+ } catch {
46423
+ return [];
46424
+ }
46425
+ }
46426
+ async function listComponents(componentsDir, prefix = "") {
46427
+ let entries;
46428
+ try {
46429
+ entries = await readdir10(componentsDir, { withFileTypes: true });
46430
+ } catch {
46431
+ return [];
46432
+ }
46433
+ const out = [];
46434
+ for (const entry of entries) {
46435
+ const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
46436
+ if (entry.isDirectory()) out.push(...await listComponents(path40.join(componentsDir, entry.name), rel));
46437
+ else if (entry.name.endsWith(".astro")) out.push(rel);
46438
+ }
46439
+ return out.sort();
46440
+ }
46441
+ function resolveForkName(requested, available) {
46442
+ const cleaned = requested.replace(/^\.?\/?(_components\/)?/, "");
46443
+ for (const candidate of [cleaned, `${cleaned}.astro`]) {
46444
+ if (available.includes(candidate)) return candidate;
46445
+ }
46446
+ const basename4 = (p) => p.slice(p.lastIndexOf("/") + 1);
46447
+ const matches = available.filter((a) => basename4(a) === cleaned || basename4(a) === `${cleaned}.astro`);
46448
+ return matches.length === 1 ? matches[0] ?? null : null;
46449
+ }
46450
+ function parseForks(fork) {
46451
+ const list = Array.isArray(fork) ? fork : fork === void 0 || fork === null ? [] : [fork];
46452
+ const names = list.filter((s) => typeof s === "string").flatMap((s) => s.split(",")).map((s) => s.trim()).filter((s) => s.length > 0);
46453
+ return [...new Set(names)];
46454
+ }
46455
+ function buildVariantDefinition(controlDefinition, opts) {
46456
+ let out = controlDefinition;
46457
+ out = out.replace(/^internalId:.*$/m, `internalId: "${opts.internalId}"`);
46458
+ out = out.replace(/^internalTitle:\s*(.*)$/m, (_m, title) => `internalTitle: ${title.trim()} (variant)`);
46459
+ const shared = opts.forks.length > 0 ? opts.forks.join(", ") : "nothing yet";
46460
+ const note = [
46461
+ "",
46462
+ `## A/B test \u2014 alternative version of \`${opts.controlSlug}\``,
46463
+ "",
46464
+ `This page is the alternative arm of a test against \`${opts.controlSlug}\`. Visitors are split at the edge and the address bar keeps saying \`/${opts.controlSlug}/\`, so both arms are the same page seen by different people.`,
46465
+ "",
46466
+ `**Only this differs:** ${shared}. Every other section is imported from \`${opts.controlSlug}\`, on purpose \u2014 an edit to the control reaches both arms, so the test keeps measuring the one difference rather than a drift between two copies.`,
46467
+ ...opts.because ? ["", `**Because:** ${opts.because}`] : [],
46468
+ ...opts.change ? ["", `**We changed:** ${opts.change}`] : [],
46469
+ "",
46470
+ `Run \`baker experiment start --landing ${opts.controlSlug} --variant ${opts.variantSlug} --because "\u2026" --change "\u2026"\` to begin, and read the result with \`baker experiment status\`.`,
46471
+ ""
46472
+ ].join("\n");
46473
+ return `${out.trimEnd()}
46474
+ ${note}`;
46475
+ }
46476
+ async function resolveTargets(projectRoot, opts) {
46477
+ const { controlSlug, variantSlug } = opts;
46478
+ for (const [label, slug] of [
46479
+ ["control", controlSlug],
46480
+ ["slug", variantSlug]
46481
+ ]) {
46482
+ if (!SLUG_RE2.test(slug)) {
46483
+ fail7(
46484
+ "INVALID_SLUG",
46485
+ `"${slug}" is not a landing slug \u2014 use the folder name directly under src/pages/ (lowercase letters, digits and dashes).`,
46486
+ { argument: label, availableSlugs: await listLandingSlugs2(projectRoot) }
46487
+ );
46488
+ }
46489
+ }
46490
+ if (controlSlug === variantSlug) {
46491
+ fail7("INVALID_SLUG", `The alternative needs its own slug \u2014 try \`${controlSlug}-b\`.`);
46492
+ }
46493
+ const controlDir = path40.resolve(projectRoot, "src", "pages", controlSlug);
46494
+ const variantDir = path40.resolve(projectRoot, "src", "pages", variantSlug);
46495
+ if (!await isDir2(controlDir)) {
46496
+ fail7("NOT_FOUND", `No landing at src/pages/${controlSlug}/`, {
46497
+ availableSlugs: await listLandingSlugs2(projectRoot)
46498
+ });
46499
+ }
46500
+ if (await exists(variantDir)) {
46501
+ fail7(
46502
+ "ALREADY_EXISTS",
46503
+ `src/pages/${variantSlug}/ already exists \u2014 pick another slug, or delete that page if it was a false start.`
46504
+ );
46505
+ }
46506
+ if (!await exists(path40.join(controlDir, "index.astro"))) {
46507
+ fail7("NOT_FOUND", `src/pages/${controlSlug}/index.astro is missing, so there is no page to make a variant of.`);
46508
+ }
46509
+ const available = await listComponents(path40.join(controlDir, "_components"));
46510
+ const forks = [];
46511
+ for (const name of parseForks(opts.fork)) {
46512
+ const resolved = resolveForkName(name, available);
46513
+ if (resolved === null) {
46514
+ fail7("NOT_FOUND", `"${name}" is not a component of ${controlSlug}.`, {
46515
+ availableComponents: available,
46516
+ hint: "Name it as it appears under _components/ \u2014 `Hero.astro`, or `hero/Card.astro`."
46517
+ });
46518
+ }
46519
+ forks.push(resolved);
46520
+ }
46521
+ return { controlDir, variantDir, available, forks };
46522
+ }
46523
+ async function writeVariant(opts) {
46524
+ const { controlDir, variantDir, controlSlug, variantSlug, forks } = opts;
46525
+ const forkedTargets = new Set(forks.map((f) => path40.join(controlDir, "_components", f)));
46526
+ const written = [];
46527
+ const indexText = await readFile25(path40.join(controlDir, "index.astro"), "utf8");
46528
+ await mkdir13(variantDir, { recursive: true });
46529
+ await writeFile18(
46530
+ path40.join(variantDir, "index.astro"),
46531
+ repointFile(indexText, { fromDir: controlDir, toDir: variantDir, controlDir, variantDir, forkedTargets }),
46532
+ "utf8"
46533
+ );
46534
+ written.push(`src/pages/${variantSlug}/index.astro`);
46535
+ for (const fork of forks) {
46536
+ const fromFile = path40.join(controlDir, "_components", fork);
46537
+ const toFile = path40.join(variantDir, "_components", fork);
46538
+ const text2 = await readFile25(fromFile, "utf8");
46539
+ await mkdir13(path40.dirname(toFile), { recursive: true });
46540
+ await writeFile18(
46541
+ toFile,
46542
+ repointFile(text2, {
46543
+ fromDir: path40.dirname(fromFile),
46544
+ toDir: path40.dirname(toFile),
46545
+ controlDir,
46546
+ variantDir,
46547
+ forkedTargets
46548
+ }),
46549
+ "utf8"
46550
+ );
46551
+ written.push(`src/pages/${variantSlug}/_components/${fork}`);
46552
+ }
46553
+ const controlDefinitionPath = path40.join(controlDir, "_definition.md");
46554
+ if (await exists(controlDefinitionPath)) {
46555
+ const definition = buildVariantDefinition(await readFile25(controlDefinitionPath, "utf8"), {
46556
+ internalId: randomUUID2().replace(/-/g, "").slice(0, 8),
46557
+ variantSlug,
46558
+ controlSlug,
46559
+ forks,
46560
+ ...opts.because ? { because: opts.because } : {},
46561
+ ...opts.change ? { change: opts.change } : {}
46562
+ });
46563
+ await writeFile18(path40.join(variantDir, "_definition.md"), definition, "utf8");
46564
+ written.push(`src/pages/${variantSlug}/_definition.md`);
46565
+ }
46566
+ await mkdir13(path40.join(variantDir, "_images"), { recursive: true });
46567
+ await writeFile18(path40.join(variantDir, "_images", ".gitkeep"), "", "utf8");
46568
+ return written;
46569
+ }
46570
+ var variantCommand = defineCommand167({
46571
+ meta: {
46572
+ name: "variant",
46573
+ description: "Create the alternative version of a landing for an A/B test. The new page SHARES the control's sections and forks only the component you name, so the two arms differ by exactly the thing you are testing. Run `baker experiment plan` first \u2014 most pages do not have the traffic to settle most questions."
46574
+ },
46575
+ args: {
46576
+ control: { type: "positional", required: true, description: "The page under test, by slug" },
46577
+ slug: {
46578
+ type: "positional",
46579
+ required: true,
46580
+ description: "Slug for the alternative page (convention: <control>-b)"
46581
+ },
46582
+ fork: {
46583
+ type: "string",
46584
+ description: "Component the variant owns its own copy of (`Hero.astro`). Repeat for several."
46585
+ },
46586
+ because: {
46587
+ type: "string",
46588
+ required: true,
46589
+ description: "What you SAW that makes this worth building \u2014 the observation, not the change. Required here, before the page exists, because a reason written afterwards is a caption for a page rather than the reason for one, and a test with no stated belief teaches nothing whichever way it lands. Recorded in the page's notes and passed to `baker experiment start`."
46590
+ },
46591
+ change: {
46592
+ type: "string",
46593
+ required: true,
46594
+ description: "What is different about this version, in the client's words \u2014 \u201Cput the booking form in the hero\u201D."
46595
+ }
46596
+ },
46597
+ async run({ args }) {
46598
+ const projectRoot = process.cwd();
46599
+ const controlSlug = String(args.control);
46600
+ const variantSlug = String(args.slug);
46601
+ const { controlDir, variantDir, available, forks } = await resolveTargets(projectRoot, {
46602
+ controlSlug,
46603
+ variantSlug,
46604
+ fork: args.fork
46605
+ });
46606
+ const written = await writeVariant({
46607
+ controlDir,
46608
+ variantDir,
46609
+ controlSlug,
46610
+ variantSlug,
46611
+ forks,
46612
+ because: String(args.because),
46613
+ change: String(args.change)
46614
+ });
46615
+ const hints = [
46616
+ forks.length === 0 ? `MISSING --fork: ${variantSlug} currently renders exactly ${controlSlug}, so a test between them cannot conclude anything. Re-run with --fork <Component>, or copy the one section your change touches into src/pages/${variantSlug}/_components/ and repoint that import.` : `Edit ONLY src/pages/${variantSlug}/_components/${forks.join(", ")} \u2014 every other section is shared with ${controlSlug}, and editing a shared one changes both arms at once.`,
46617
+ `Preview both at http://localhost:4321/${controlSlug}/ and http://localhost:4321/${variantSlug}/ and check they differ only where you meant.`,
46618
+ `Then: baker experiment start --landing ${controlSlug} --variant ${variantSlug} --because "${String(args.because)}" --change "${String(args.change)}". Nothing is split until the session is published \u2014 the person publishing is the review of this page.`
46619
+ ];
46620
+ process.stdout.write(
46621
+ `${JSON.stringify(
46622
+ {
46623
+ ok: true,
46624
+ data: {
46625
+ control: controlSlug,
46626
+ slug: variantSlug,
46627
+ forked: forks,
46628
+ shared: available.filter((c) => !forks.includes(c)),
46629
+ written
46630
+ },
46631
+ hints
46632
+ },
46633
+ null,
46634
+ 2
46635
+ )}
46636
+ `
46637
+ );
46638
+ }
46639
+ });
46640
+
45572
46641
  // src/commands/landing/index.ts
45573
- var landingCommand = defineCommand166({
46642
+ var landingCommand = defineCommand168({
45574
46643
  meta: {
45575
46644
  name: "landing",
45576
46645
  description: `Design-quality tools for landing pages (src/pages/<slug>/).
@@ -45579,16 +46648,18 @@ Start here: \`baker landing critique <slug>\` after building or editing a landin
45579
46648
 
45580
46649
  Subcommands:
45581
46650
  baker landing inspiration \u2014 reference library of real landing-page sections; search it during research, BEFORE writing the Direction Contract. Inspiration only: take the mechanism, never the words.
46651
+ baker landing variant <control> <slug> \u2014 the alternative version of a page for an A/B test: shares the control's sections, forks only the component you name, so the two arms differ by exactly your hypothesis. Run \`baker experiment plan\` first.
45582
46652
  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.`
45583
46653
  },
45584
46654
  subCommands: {
45585
46655
  inspiration: inspirationCommand,
45586
- critique: critiqueCommand2
46656
+ critique: critiqueCommand2,
46657
+ variant: variantCommand
45587
46658
  }
45588
46659
  });
45589
46660
 
45590
46661
  // src/commands/mcp/index.ts
45591
- import { defineCommand as defineCommand167 } from "citty";
46662
+ import { defineCommand as defineCommand169 } from "citty";
45592
46663
 
45593
46664
  // src/commands/mcp/platforms.ts
45594
46665
  function readsKey(label) {
@@ -45643,7 +46714,7 @@ function parseHeaders(raw) {
45643
46714
  }
45644
46715
  return Object.keys(headers).length > 0 ? headers : void 0;
45645
46716
  }
45646
- function fail6(err) {
46717
+ function fail8(err) {
45647
46718
  if (err instanceof ApiError) {
45648
46719
  writeJson({ ok: false, error: { code: err.code, message: err.message } });
45649
46720
  process.exit(1);
@@ -45657,7 +46728,7 @@ registerSchema({
45657
46728
  description: "List everything this chat can reach: managed integrations (Attio, Slack, Gmail, Google Sheets, \u2026), custom MCP servers, and the platforms the company signed in to (HubSpot, Google Ads, GA4, Search Console, Tag Manager) which you read through their own `baker` commands. Start here when the user mentions an external tool or platform.",
45658
46729
  args: {}
45659
46730
  });
45660
- var connectedCommand = defineCommand167({
46731
+ var connectedCommand = defineCommand169({
45661
46732
  meta: {
45662
46733
  name: "connected",
45663
46734
  description: `Everything this chat can reach \u2014 managed integrations, custom MCP servers, and connected platforms.
@@ -45707,7 +46778,7 @@ A tool or platform the user names that is NOT listed anywhere here is simply not
45707
46778
  hints
45708
46779
  });
45709
46780
  } catch (err) {
45710
- fail6(err);
46781
+ fail8(err);
45711
46782
  }
45712
46783
  }
45713
46784
  });
@@ -45716,7 +46787,7 @@ registerSchema({
45716
46787
  description: "List the custom MCP servers this company's chats see (org + company + your own user scope).",
45717
46788
  args: {}
45718
46789
  });
45719
- var listCommand14 = defineCommand167({
46790
+ var listCommand14 = defineCommand169({
45720
46791
  meta: { name: "list", description: "List custom MCP servers visible to this company's chats." },
45721
46792
  run: async () => {
45722
46793
  try {
@@ -45735,7 +46806,7 @@ var listCommand14 = defineCommand167({
45735
46806
  } : {}
45736
46807
  });
45737
46808
  } catch (err) {
45738
- fail6(err);
46809
+ fail8(err);
45739
46810
  }
45740
46811
  }
45741
46812
  });
@@ -45753,7 +46824,7 @@ registerSchema({
45753
46824
  header: { type: "string", description: 'Auth header "Key: Value" (repeatable)', required: false }
45754
46825
  }
45755
46826
  });
45756
- var addCommand2 = defineCommand167({
46827
+ var addCommand2 = defineCommand169({
45757
46828
  meta: {
45758
46829
  name: "add",
45759
46830
  description: `Register a custom MCP server. Tools appear as mcp__<name>__* on the NEXT message.
@@ -45796,7 +46867,7 @@ Examples:
45796
46867
  ]
45797
46868
  });
45798
46869
  } catch (err) {
45799
- fail6(err);
46870
+ fail8(err);
45800
46871
  }
45801
46872
  }
45802
46873
  });
@@ -45805,7 +46876,7 @@ registerSchema({
45805
46876
  description: "Remove a company custom MCP server by name.",
45806
46877
  args: { name: { type: "string", description: "Server name to remove", required: true } }
45807
46878
  });
45808
- var removeCommand4 = defineCommand167({
46879
+ var removeCommand4 = defineCommand169({
45809
46880
  meta: {
45810
46881
  name: "remove",
45811
46882
  description: `Remove a company custom MCP server by name.
@@ -45823,11 +46894,11 @@ Example:
45823
46894
  });
45824
46895
  writeJson({ ok: true, data });
45825
46896
  } catch (err) {
45826
- fail6(err);
46897
+ fail8(err);
45827
46898
  }
45828
46899
  }
45829
46900
  });
45830
- var mcpCommand = defineCommand167({
46901
+ var mcpCommand = defineCommand169({
45831
46902
  meta: {
45832
46903
  name: "mcp",
45833
46904
  description: `Third-party tools for this company \u2014 see what's connected, register custom HTTPS MCP endpoints.
@@ -45853,10 +46924,10 @@ Full guide: __tooling__/docs/tools/baker/mcp.md`
45853
46924
  });
45854
46925
 
45855
46926
  // src/commands/research/index.ts
45856
- import { defineCommand as defineCommand179 } from "citty";
46927
+ import { defineCommand as defineCommand181 } from "citty";
45857
46928
 
45858
46929
  // src/commands/research/advertisers.ts
45859
- import { defineCommand as defineCommand168 } from "citty";
46930
+ import { defineCommand as defineCommand170 } from "citty";
45860
46931
 
45861
46932
  // src/commands/research/hints.ts
45862
46933
  var AD_COPY_ROUTE = 'This returns competing DOMAINS and their SERP economics \u2014 no ad copy, no headlines, no creative. For the actual copy of a competitor\'s ads: `baker winning-ads advertisers "<brand>"` \u2192 `baker winning-ads search "<brief>" --advertiser-id <id>` \u2192 `baker winning-ads content <adId>` (`primary_text` / `headline` / `cta`, plus the spoken transcript and on-screen text for video). That corpus is Meta and LinkedIn only \u2014 Google SERP ad copy is not available through any Baker command, so do not keep querying for it here.';
@@ -46039,7 +47110,7 @@ var FIELDS3 = {
46039
47110
  etv: "Estimated traffic value (USD)",
46040
47111
  visibility: "SERP visibility score (0-1)"
46041
47112
  };
46042
- var advertisersCommand = defineCommand168({
47113
+ var advertisersCommand = defineCommand170({
46043
47114
  meta: {
46044
47115
  name: "advertisers",
46045
47116
  description: `Domains competing for a keyword in Google SERPs, with position, relevance, traffic value and visibility. Returns NO ad copy \u2014 for a competitor's headlines and body copy use \`baker winning-ads content <adId>\` (Meta/LinkedIn only).
@@ -46095,7 +47166,7 @@ Examples:
46095
47166
  });
46096
47167
 
46097
47168
  // src/commands/research/autocomplete.ts
46098
- import { defineCommand as defineCommand169 } from "citty";
47169
+ import { defineCommand as defineCommand171 } from "citty";
46099
47170
  registerSchema({
46100
47171
  command: "research.autocomplete",
46101
47172
  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).",
@@ -46118,7 +47189,7 @@ registerSchema({
46118
47189
  var FIELDS4 = {
46119
47190
  suggestion: "Autocomplete suggestion from Google"
46120
47191
  };
46121
- var autocompleteCommand = defineCommand169({
47192
+ var autocompleteCommand = defineCommand171({
46122
47193
  meta: {
46123
47194
  name: "autocomplete",
46124
47195
  description: `Get Google Autocomplete suggestions for keyword expansion.
@@ -46173,7 +47244,7 @@ Examples:
46173
47244
  });
46174
47245
 
46175
47246
  // src/commands/research/countries.ts
46176
- import { defineCommand as defineCommand170 } from "citty";
47247
+ import { defineCommand as defineCommand172 } from "citty";
46177
47248
  registerSchema({
46178
47249
  command: "research.countries",
46179
47250
  description: "List all supported country codes for --location flag in research commands.",
@@ -46230,7 +47301,7 @@ var FIELDS5 = {
46230
47301
  code: "Country code to pass as --location",
46231
47302
  name: "Country name"
46232
47303
  };
46233
- var countriesCommand = defineCommand170({
47304
+ var countriesCommand = defineCommand172({
46234
47305
  meta: {
46235
47306
  name: "countries",
46236
47307
  description: "List all supported country codes for --location flag."
@@ -46241,7 +47312,7 @@ var countriesCommand = defineCommand170({
46241
47312
  });
46242
47313
 
46243
47314
  // src/commands/research/fetch.ts
46244
- import { defineCommand as defineCommand171 } from "citty";
47315
+ import { defineCommand as defineCommand173 } from "citty";
46245
47316
  var CONTENT_PREVIEW_CHARS = 2e4;
46246
47317
  var TIMEOUT_MS = 18e4;
46247
47318
  registerSchema({
@@ -46314,7 +47385,7 @@ function fetchFix(code) {
46314
47385
  explanation: "This read failed. Don't build a retry ladder around it \u2014 finish the rest of the job with what you can reach and name this page as a gap."
46315
47386
  };
46316
47387
  }
46317
- var fetchCommand = defineCommand171({
47388
+ var fetchCommand = defineCommand173({
46318
47389
  meta: {
46319
47390
  name: "fetch",
46320
47391
  description: `Read a page an ordinary web fetch could not. Bot walls and JavaScript-rendered pages are resolved for you \u2014 you never have to retry, wait, or drive a browser yourself.
@@ -46391,7 +47462,7 @@ Examples:
46391
47462
  });
46392
47463
 
46393
47464
  // src/commands/research/intent.ts
46394
- import { defineCommand as defineCommand172 } from "citty";
47465
+ import { defineCommand as defineCommand174 } from "citty";
46395
47466
  registerSchema({
46396
47467
  command: "research.intent",
46397
47468
  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.",
@@ -46414,7 +47485,7 @@ var FIELDS7 = {
46414
47485
  intent: "Primary Google Search intent: informational, navigational, commercial, transactional",
46415
47486
  probability: "Confidence score 0.0-1.0"
46416
47487
  };
46417
- var intentCommand = defineCommand172({
47488
+ var intentCommand = defineCommand174({
46418
47489
  meta: {
46419
47490
  name: "intent",
46420
47491
  description: `Classify Google Search intent for keywords. Returns intent type and confidence.
@@ -46462,7 +47533,7 @@ Examples:
46462
47533
  });
46463
47534
 
46464
47535
  // src/commands/research/keyword-gap.ts
46465
- import { defineCommand as defineCommand173 } from "citty";
47536
+ import { defineCommand as defineCommand175 } from "citty";
46466
47537
  registerSchema({
46467
47538
  command: "research.keyword-gap",
46468
47539
  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.",
@@ -46491,7 +47562,7 @@ var FIELDS8 = {
46491
47562
  cpc: "Cost per click USD",
46492
47563
  their_position: "Competitor's ranking position"
46493
47564
  };
46494
- var keywordGapCommand = defineCommand173({
47565
+ var keywordGapCommand = defineCommand175({
46495
47566
  meta: {
46496
47567
  name: "keyword-gap",
46497
47568
  description: `Find keywords a competitor has that you don't. Supports pagination via --offset.
@@ -46566,7 +47637,7 @@ Examples:
46566
47637
  });
46567
47638
 
46568
47639
  // src/commands/research/keywords-for-site.ts
46569
- import { defineCommand as defineCommand174 } from "citty";
47640
+ import { defineCommand as defineCommand176 } from "citty";
46570
47641
  registerSchema({
46571
47642
  command: "research.keywords-for-site",
46572
47643
  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.",
@@ -46599,7 +47670,7 @@ var FIELDS9 = {
46599
47670
  competition: "LOW, MEDIUM, or HIGH",
46600
47671
  competition_index: "Competition score 0-100"
46601
47672
  };
46602
- var keywordsForSiteCommand = defineCommand174({
47673
+ var keywordsForSiteCommand = defineCommand176({
46603
47674
  meta: {
46604
47675
  name: "keywords-for-site",
46605
47676
  description: `Get keywords a competitor targets in Google. Use --type to filter paid/organic.
@@ -46661,7 +47732,7 @@ Examples:
46661
47732
  });
46662
47733
 
46663
47734
  // src/commands/research/languages.ts
46664
- import { defineCommand as defineCommand175 } from "citty";
47735
+ import { defineCommand as defineCommand177 } from "citty";
46665
47736
  registerSchema({
46666
47737
  command: "research.languages",
46667
47738
  description: "List all supported language codes for --language flag in research commands.",
@@ -46691,7 +47762,7 @@ var FIELDS10 = {
46691
47762
  code: "Language code to pass as --language",
46692
47763
  name: "Language name (also accepted by --language)"
46693
47764
  };
46694
- var languagesCommand2 = defineCommand175({
47765
+ var languagesCommand2 = defineCommand177({
46695
47766
  meta: {
46696
47767
  name: "languages",
46697
47768
  description: "List all supported language codes for --language flag."
@@ -46702,7 +47773,7 @@ var languagesCommand2 = defineCommand175({
46702
47773
  });
46703
47774
 
46704
47775
  // src/commands/research/lighthouse.ts
46705
- import { defineCommand as defineCommand176 } from "citty";
47776
+ import { defineCommand as defineCommand178 } from "citty";
46706
47777
  registerSchema({
46707
47778
  command: "research.lighthouse",
46708
47779
  description: "Landing page performance audit. Returns metrics that affect Google Ads Quality Score and CPC.",
@@ -46721,7 +47792,7 @@ var FIELDS11 = {
46721
47792
  speed_index_ms: "Speed Index in ms (good: < 3400)",
46722
47793
  interactive_ms: "Time to Interactive in ms (good: < 3800)"
46723
47794
  };
46724
- var lighthouseCommand = defineCommand176({
47795
+ var lighthouseCommand = defineCommand178({
46725
47796
  meta: {
46726
47797
  name: "lighthouse",
46727
47798
  description: `Landing page performance audit. Metrics affecting Google Ads Quality Score.
@@ -46759,7 +47830,7 @@ Examples:
46759
47830
  });
46760
47831
 
46761
47832
  // src/commands/research/relevant-pages.ts
46762
- import { defineCommand as defineCommand177 } from "citty";
47833
+ import { defineCommand as defineCommand179 } from "citty";
46763
47834
  registerSchema({
46764
47835
  command: "research.relevant-pages",
46765
47836
  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).",
@@ -46785,7 +47856,7 @@ var FIELDS12 = {
46785
47856
  keywords: "Total organic keywords the page ranks for",
46786
47857
  top_10: "Keywords in positions 1-10"
46787
47858
  };
46788
- var relevantPagesCommand = defineCommand177({
47859
+ var relevantPagesCommand = defineCommand179({
46789
47860
  meta: {
46790
47861
  name: "relevant-pages",
46791
47862
  description: `Get the top pages of a competitor domain with traffic data.
@@ -46832,7 +47903,7 @@ Examples:
46832
47903
  });
46833
47904
 
46834
47905
  // src/commands/research/web.ts
46835
- import { defineCommand as defineCommand178 } from "citty";
47906
+ import { defineCommand as defineCommand180 } from "citty";
46836
47907
  registerSchema({
46837
47908
  command: "research.web",
46838
47909
  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).",
@@ -46883,7 +47954,7 @@ async function runDeepResearch(question) {
46883
47954
  }
46884
47955
  throw new Error("Deep research timed out");
46885
47956
  }
46886
- var webCommand = defineCommand178({
47957
+ var webCommand = defineCommand180({
46887
47958
  meta: {
46888
47959
  name: "web",
46889
47960
  description: `Search the web with AI to answer any open-ended marketing question. Uses live internet data via Google Search.
@@ -46945,7 +48016,7 @@ Examples:
46945
48016
  });
46946
48017
 
46947
48018
  // src/commands/research/index.ts
46948
- var researchCommand = defineCommand179({
48019
+ var researchCommand = defineCommand181({
46949
48020
  meta: {
46950
48021
  name: "research",
46951
48022
  description: `Competitive intelligence and AI-powered research commands.
@@ -46989,10 +48060,10 @@ Full guide: __tooling__/docs/tools/baker/research.md`
46989
48060
  });
46990
48061
 
46991
48062
  // src/commands/scheduled-actions/index.ts
46992
- import { defineCommand as defineCommand187 } from "citty";
48063
+ import { defineCommand as defineCommand189 } from "citty";
46993
48064
 
46994
48065
  // src/commands/scheduled-actions/create.ts
46995
- import { defineCommand as defineCommand180 } from "citty";
48066
+ import { defineCommand as defineCommand182 } from "citty";
46996
48067
 
46997
48068
  // src/commands/scheduled-actions/shared.ts
46998
48069
  var TEMP_SCHEDULED_ACTION_PREFIX = "temp_sched_";
@@ -47139,7 +48210,7 @@ registerSchema({
47139
48210
  }
47140
48211
  }
47141
48212
  });
47142
- var createCommand3 = defineCommand180({
48213
+ var createCommand3 = defineCommand182({
47143
48214
  meta: {
47144
48215
  name: "create",
47145
48216
  description: 'Stage a scheduled action. Example: baker scheduled-actions create --name "Weekly report" --description "..." --cron "0 9 * * MON"'
@@ -47198,7 +48269,7 @@ var createCommand3 = defineCommand180({
47198
48269
  });
47199
48270
 
47200
48271
  // src/commands/scheduled-actions/delete.ts
47201
- import { defineCommand as defineCommand181 } from "citty";
48272
+ import { defineCommand as defineCommand183 } from "citty";
47202
48273
  registerSchema({
47203
48274
  command: "scheduled-actions.delete",
47204
48275
  description: "Stage deletion of a published scheduled action or cancellation of a temp_sched_* draft creation.",
@@ -47206,7 +48277,7 @@ registerSchema({
47206
48277
  id: { type: "string", description: "Published scheduled action ID or temp_sched_* draft ID", required: true }
47207
48278
  }
47208
48279
  });
47209
- var deleteCommand3 = defineCommand181({
48280
+ var deleteCommand3 = defineCommand183({
47210
48281
  meta: {
47211
48282
  name: "delete",
47212
48283
  description: "Stage scheduled action deletion. Example: baker scheduled-actions delete <id-or-temp_sched_id>"
@@ -47235,7 +48306,7 @@ var deleteCommand3 = defineCommand181({
47235
48306
  });
47236
48307
 
47237
48308
  // src/commands/scheduled-actions/get.ts
47238
- import { defineCommand as defineCommand182 } from "citty";
48309
+ import { defineCommand as defineCommand184 } from "citty";
47239
48310
  registerSchema({
47240
48311
  command: "scheduled-actions.get",
47241
48312
  description: "Get a published scheduled action or a temp_sched_* draft-created scheduled action.",
@@ -47244,7 +48315,7 @@ registerSchema({
47244
48315
  chat: { type: "string", description: CHAT_READ_ARG.description, required: false }
47245
48316
  }
47246
48317
  });
47247
- var getCommand4 = defineCommand182({
48318
+ var getCommand4 = defineCommand184({
47248
48319
  meta: {
47249
48320
  name: "get",
47250
48321
  description: "Get a scheduled action. Example: baker scheduled-actions get <id-or-temp_sched_id>"
@@ -47283,7 +48354,7 @@ var getCommand4 = defineCommand182({
47283
48354
  });
47284
48355
 
47285
48356
  // src/commands/scheduled-actions/list.ts
47286
- import { defineCommand as defineCommand183 } from "citty";
48357
+ import { defineCommand as defineCommand185 } from "citty";
47287
48358
  registerSchema({
47288
48359
  command: "scheduled-actions.list",
47289
48360
  description: "List published scheduled actions. Includes draft state when BAKER_CHAT_ID is set, or --chat <id> to read an earlier chat's staged schedules instead.",
@@ -47291,7 +48362,7 @@ registerSchema({
47291
48362
  chat: { type: "string", description: CHAT_READ_ARG.description, required: false }
47292
48363
  }
47293
48364
  });
47294
- var listCommand15 = defineCommand183({
48365
+ var listCommand15 = defineCommand185({
47295
48366
  meta: {
47296
48367
  name: "list",
47297
48368
  description: "List scheduled actions. Includes staged draft ops when BAKER_CHAT_ID is set, or --chat <id> to read an earlier chat's staged schedules instead."
@@ -47314,9 +48385,9 @@ var listCommand15 = defineCommand183({
47314
48385
  });
47315
48386
 
47316
48387
  // src/commands/scheduled-actions/templates.ts
47317
- import { readFile as readFile24 } from "fs/promises";
47318
- import path38 from "path";
47319
- import { defineCommand as defineCommand184 } from "citty";
48388
+ import { readFile as readFile26 } from "fs/promises";
48389
+ import path41 from "path";
48390
+ import { defineCommand as defineCommand186 } from "citty";
47320
48391
  registerSchema({
47321
48392
  command: "scheduled-actions.templates",
47322
48393
  description: "The recipes available to this company: the ones Baker ships plus the ones they wrote themselves, each with its id, what it produces and how often it is meant to run. Read this before proposing a company's automation, so every recipe you name is one that exists. Also how a company gets a recipe of its own \u2014 save a brief, prove it runs, then publish it.",
@@ -47371,7 +48442,7 @@ registerSchema({
47371
48442
  }
47372
48443
  }
47373
48444
  });
47374
- var templatesCommand = defineCommand184({
48445
+ var templatesCommand = defineCommand186({
47375
48446
  meta: {
47376
48447
  name: "templates",
47377
48448
  description: `The recipes this company can run \u2014 Baker's own plus theirs, with what each one produces.
@@ -47418,7 +48489,7 @@ Full guide: __tooling__/docs/tools/baker/scheduled-actions.md`
47418
48489
  }
47419
48490
  if (save.length > 0) {
47420
48491
  const briefFile = flag("brief-file");
47421
- const brief = briefFile.length > 0 ? await readFile24(path38.resolve(briefFile), "utf8") : flag("brief");
48492
+ const brief = briefFile.length > 0 ? await readFile26(path41.resolve(briefFile), "utf8") : flag("brief");
47422
48493
  if (brief.trim().length === 0) {
47423
48494
  failValidation4("--brief-file (preferred) or --brief is required: the brief is the recipe.");
47424
48495
  }
@@ -47463,7 +48534,7 @@ Full guide: __tooling__/docs/tools/baker/scheduled-actions.md`
47463
48534
  });
47464
48535
 
47465
48536
  // src/commands/scheduled-actions/trigger.ts
47466
- import { defineCommand as defineCommand185 } from "citty";
48537
+ import { defineCommand as defineCommand187 } from "citty";
47467
48538
  registerSchema({
47468
48539
  command: "scheduled-actions.trigger",
47469
48540
  description: "Immediately trigger a published scheduled action. Does not require BAKER_CHAT_ID and rejects temp_sched_* IDs.",
@@ -47471,7 +48542,7 @@ registerSchema({
47471
48542
  id: { type: "string", description: "Published scheduled action ID", required: true }
47472
48543
  }
47473
48544
  });
47474
- var triggerCommand = defineCommand185({
48545
+ var triggerCommand = defineCommand187({
47475
48546
  meta: {
47476
48547
  name: "trigger",
47477
48548
  description: "Immediately trigger a published scheduled action. Example: baker scheduled-actions trigger <id>"
@@ -47508,7 +48579,7 @@ var triggerCommand = defineCommand185({
47508
48579
  });
47509
48580
 
47510
48581
  // src/commands/scheduled-actions/update.ts
47511
- import { defineCommand as defineCommand186 } from "citty";
48582
+ import { defineCommand as defineCommand188 } from "citty";
47512
48583
  registerSchema({
47513
48584
  command: "scheduled-actions.update",
47514
48585
  description: "Stage an update to a published scheduled action or temp_sched_* draft-created scheduled action.",
@@ -47539,7 +48610,7 @@ registerSchema({
47539
48610
  prompt: { type: "string", description: "Replacement additional spawned-agent instructions", required: false }
47540
48611
  }
47541
48612
  });
47542
- var updateCommand3 = defineCommand186({
48613
+ var updateCommand3 = defineCommand188({
47543
48614
  meta: {
47544
48615
  name: "update",
47545
48616
  description: "Stage a scheduled action update. Examples: baker scheduled-actions update <id> --enabled false | baker scheduled-actions update <id> --mode publish"
@@ -47617,7 +48688,7 @@ var updateCommand3 = defineCommand186({
47617
48688
  });
47618
48689
 
47619
48690
  // src/commands/scheduled-actions/index.ts
47620
- var scheduledActionsCommand = defineCommand187({
48691
+ var scheduledActionsCommand = defineCommand189({
47621
48692
  meta: {
47622
48693
  name: "scheduled-actions",
47623
48694
  description: `Manage Scheduled Actions. Subcommands: list, get, create, update, delete, trigger, templates.
@@ -47646,14 +48717,14 @@ Full guide: __tooling__/docs/tools/baker/scheduled-actions.md`
47646
48717
  });
47647
48718
 
47648
48719
  // src/commands/schema.ts
47649
- import { defineCommand as defineCommand188 } from "citty";
48720
+ import { defineCommand as defineCommand190 } from "citty";
47650
48721
  function narrowToFamily(commandName, available) {
47651
48722
  const segments = commandName.split(".");
47652
48723
  const prefix = segments[0] === "ads" && segments[1] ? `ads.${segments[1]}.` : `${segments[0]}.`;
47653
48724
  const siblings = available.filter((name) => name.startsWith(prefix));
47654
48725
  return siblings.length > 0 ? siblings : available;
47655
48726
  }
47656
- var schemaCommand2 = defineCommand188({
48727
+ var schemaCommand2 = defineCommand190({
47657
48728
  meta: {
47658
48729
  name: "schema",
47659
48730
  description: "Inspect command argument schemas (for AI agent introspection). Lists all commands if no argument given. Example: baker schema images.search"
@@ -47697,10 +48768,10 @@ var schemaCommand2 = defineCommand188({
47697
48768
  });
47698
48769
 
47699
48770
  // src/commands/studio/index.ts
47700
- import { defineCommand as defineCommand197 } from "citty";
48771
+ import { defineCommand as defineCommand199 } from "citty";
47701
48772
 
47702
48773
  // src/commands/studio/animate.ts
47703
- import { defineCommand as defineCommand189 } from "citty";
48774
+ import { defineCommand as defineCommand191 } from "citty";
47704
48775
 
47705
48776
  // src/commands/studio/batch.ts
47706
48777
  function projectBatch(generation, full) {
@@ -47889,7 +48960,7 @@ function parseImageRefs(spec) {
47889
48960
  }
47890
48961
  var defaultDeps = {
47891
48962
  ingest: (url) => apiPost("/api/images/ingest", { url, source: "uploaded" }),
47892
- upload: (path40) => uploadLocalImage({ file: path40, contentType: detectImageContentType(path40), source: "uploaded" })
48963
+ upload: (path43) => uploadLocalImage({ file: path43, contentType: detectImageContentType(path43), source: "uploaded" })
47893
48964
  };
47894
48965
  async function resolveLibraryImageIds(spec, limit, deps = defaultDeps) {
47895
48966
  const refs = parseImageRefs(spec);
@@ -47909,10 +48980,10 @@ async function resolveLibraryImageIds(spec, limit, deps = defaultDeps) {
47909
48980
  }
47910
48981
  return { imageIds, added };
47911
48982
  }
47912
- function uploadFailure(path40) {
48983
+ function uploadFailure(path43) {
47913
48984
  return (error) => {
47914
48985
  if (error instanceof ApiError) throw error;
47915
- throw new ApiError("VALIDATION_ERROR", `Could not read "${path40}" as an image.`);
48986
+ throw new ApiError("VALIDATION_ERROR", `Could not read "${path43}" as an image.`);
47916
48987
  };
47917
48988
  }
47918
48989
 
@@ -48151,7 +49222,7 @@ function costHintsFor(body) {
48151
49222
  }
48152
49223
  return hints;
48153
49224
  }
48154
- var animateCommand = defineCommand189({
49225
+ var animateCommand = defineCommand191({
48155
49226
  meta: {
48156
49227
  name: "animate",
48157
49228
  description: "Render a clip. With an image the look is already fixed, so the prompt describes MOVEMENT \u2014 what the camera does, what the subject does, in what order. With --from text there is no image and the prompt is the whole shot.\n\nA rendered clip is NOT usable anywhere until you keep it: `baker studio keep <id> --slot N` is what puts it in the video library. Takes nobody keeps are never ingested, which is what makes a rejected batch cheap.\n\nFor anything longer than 15 seconds, or to build on footage that already exists, use --model bytedance/seedance-2.5: it renders 4-30s and is the only model that reads an existing clip or an existing soundtrack.\n\nExamples:\n baker studio animate 'slow push in, model turns to camera and smiles' --image j57abc123def456ghi789\n baker studio animate 'handheld drift right, steam rising from the cup' --image './out/hero.png' --duration 6 --quality 1080p\n baker studio animate 'product rotates once on a turntable' --image j57abc\u2026,j57def\u2026 --from references\n baker studio animate 'she keeps walking, camera stays with her, then she stops and looks up' --image j57abc\u2026 --from references --from-clip j57batch\u2026:0 --model bytedance/seedance-2.5 --duration 20\n baker studio animate 'hold on the product, then a slow push-in' --from references --from-video j57vid\u2026 --model bytedance/seedance-2.5\n baker studio animate 'slow drone pull-back over a solar farm at golden hour, no people' --from text --model bytedance/seedance-2.5 --duration 12"
@@ -48270,7 +49341,7 @@ var animateCommand = defineCommand189({
48270
49341
  });
48271
49342
 
48272
49343
  // src/commands/studio/generate.ts
48273
- import { defineCommand as defineCommand190 } from "citty";
49344
+ import { defineCommand as defineCommand192 } from "citty";
48274
49345
  var MODEL_LIST2 = IMAGE_MODEL_IDS;
48275
49346
  var DEFAULT_MAX_WAIT_MS2 = 24e4;
48276
49347
  registerSchema({
@@ -48406,7 +49477,7 @@ function buildGenerateBody(args, prompt) {
48406
49477
  }
48407
49478
  return body;
48408
49479
  }
48409
- var generateCommand = defineCommand190({
49480
+ var generateCommand = defineCommand192({
48410
49481
  meta: {
48411
49482
  name: "generate",
48412
49483
  description: "Start here to make an image. Renders 1-8 takes of one brief, ingests each into the media library as it lands, and shows the batch in the dashboard Studio next to the ones the client ran.\n\nModel choice: google/gemini-3.1-flash-image-preview (default \u2014 fast, best at editing a reference and at extreme ratios), google/gemini-3-pro-image-preview (highest fidelity, slower), openai/gpt-image-2 (photoreal and the cleanest in-image text \u2014 no --image-size, no 4:5 / 5:4), recraft/recraft-v4.1-pro-vector (vector/flat marks with palette control).\n\n--reference is the biggest quality lever there is: a real logo, product shot, Pinterest pin or sandbox screenshot beats any amount of adjectives.\n\nExamples:\n baker studio generate 'matte black bottle on wet marble, hard studio light, 35mm' --aspect-ratio 3:2 --count 3\n baker studio generate 'this bottle on a sunlit kitchen counter' --reference './src/brand/product.png,https://\u2026/kitchen.jpg'\n baker studio generate 'founder-style selfie, kitchen background, natural light' --skill ugc-selfie-hook\n baker studio generate 'flat geometric mascot, brand palette' --model recraft/recraft-v4.1-pro-vector --rgb-colors '[[10,10,10],[255,80,0]]'"
@@ -48484,7 +49555,7 @@ var generateCommand = defineCommand190({
48484
49555
  });
48485
49556
 
48486
49557
  // src/commands/studio/get.ts
48487
- import { defineCommand as defineCommand191 } from "citty";
49558
+ import { defineCommand as defineCommand193 } from "citty";
48488
49559
  registerSchema({
48489
49560
  command: "studio.get",
48490
49561
  description: "Read one Studio batch: every take, where it lives, and why a take is missing. This is how you pick up a batch that was still rendering when the start command returned.",
@@ -48493,7 +49564,7 @@ registerSchema({
48493
49564
  full: { type: "boolean", description: "Include settings, references and attribution", required: false }
48494
49565
  }
48495
49566
  });
48496
- var getCommand5 = defineCommand191({
49567
+ var getCommand5 = defineCommand193({
48497
49568
  meta: {
48498
49569
  name: "get",
48499
49570
  description: "Read one Studio batch \u2014 the takes, their urls, whether each is in the library, and the reason for any that failed.\n\nExample: baker studio get j57abc123def456ghi789\nExample: baker studio get j57abc123def456ghi789 --full"
@@ -48522,7 +49593,7 @@ var getCommand5 = defineCommand191({
48522
49593
  });
48523
49594
 
48524
49595
  // src/commands/studio/improve.ts
48525
- import { defineCommand as defineCommand192 } from "citty";
49596
+ import { defineCommand as defineCommand194 } from "citty";
48526
49597
  var DESCRIPTION = "Sharpen a rough brief into directed art direction \u2014 the same rewrite the client gets from the wand in the Studio prompt bar. Reach for it when you are relaying the CLIENT's own words and want them shaped without substituting your voice; when you are writing the art direction yourself, just write it, because you will do a better job than this does.";
48527
49598
  registerSchema({
48528
49599
  command: "studio.improve",
@@ -48547,7 +49618,7 @@ registerSchema({
48547
49618
  }
48548
49619
  }
48549
49620
  });
48550
- var improveCommand = defineCommand192({
49621
+ var improveCommand = defineCommand194({
48551
49622
  meta: {
48552
49623
  name: "improve",
48553
49624
  description: `${DESCRIPTION}
@@ -48591,7 +49662,7 @@ Examples:
48591
49662
  });
48592
49663
 
48593
49664
  // src/commands/studio/keep.ts
48594
- import { defineCommand as defineCommand193 } from "citty";
49665
+ import { defineCommand as defineCommand195 } from "citty";
48595
49666
  registerSchema({
48596
49667
  command: "studio.keep",
48597
49668
  description: "Mark one take as the keeper. For an image this stars it, so the client reviewing the batch sees which one you used. For a CLIP it is the step that puts it in the video library \u2014 until then the clip cannot be used in a canvas, a landing, or an ad.",
@@ -48601,7 +49672,7 @@ registerSchema({
48601
49672
  undo: { type: "boolean", description: "Un-star an image, or take a kept clip back out", required: false }
48602
49673
  }
48603
49674
  });
48604
- var keepCommand = defineCommand193({
49675
+ var keepCommand = defineCommand195({
48605
49676
  meta: {
48606
49677
  name: "keep",
48607
49678
  description: "Mark one take as the keeper. An image gets starred (it was already in the library); a clip gets INGESTED into the video library, which is what makes it usable anywhere else.\n\nExample: baker studio keep j57abc123def456ghi789 --slot 2\nExample: baker studio keep j57abc123def456ghi789 --slot 2 --undo"
@@ -48652,7 +49723,7 @@ var keepCommand = defineCommand193({
48652
49723
  });
48653
49724
 
48654
49725
  // src/commands/studio/list.ts
48655
- import { defineCommand as defineCommand194 } from "citty";
49726
+ import { defineCommand as defineCommand196 } from "citty";
48656
49727
  registerSchema({
48657
49728
  command: "studio.list",
48658
49729
  description: "Recent Studio batches for THIS conversation, newest first \u2014 what you have already generated, so you re-use a take instead of paying for it twice. `--all` widens it to everything the company generated, including what people ran themselves in the dashboard.",
@@ -48663,7 +49734,7 @@ registerSchema({
48663
49734
  full: { type: "boolean", description: "Include settings, references and attribution", required: false }
48664
49735
  }
48665
49736
  });
48666
- var listCommand16 = defineCommand194({
49737
+ var listCommand16 = defineCommand196({
48667
49738
  meta: {
48668
49739
  name: "list",
48669
49740
  description: "Recent Studio batches, newest first. Scoped to this conversation unless you pass --all.\n\nExample: baker studio list\nExample: baker studio list --kind video --limit 5\nExample: baker studio list --all # includes batches the client ran in the dashboard"
@@ -48697,7 +49768,7 @@ var listCommand16 = defineCommand194({
48697
49768
  });
48698
49769
 
48699
49770
  // src/commands/studio/models.ts
48700
- import { defineCommand as defineCommand195 } from "citty";
49771
+ import { defineCommand as defineCommand197 } from "citty";
48701
49772
  var DESCRIPTION2 = "What each Studio model actually accepts: its shapes, resolutions, clip lengths, prompt character cap, how many reference images it takes, and which knobs it has. Read this before a batch you care about \u2014 the models disagree far more than they look like they do, and a setting the chosen model does not have is REFUSED, not ignored.";
48702
49773
  registerSchema({
48703
49774
  command: "studio.models",
@@ -48784,7 +49855,7 @@ function buildModelCards(kind, model) {
48784
49855
  const selected = model ? ids.filter((id) => id === model) : ids;
48785
49856
  return selected.map((id) => build(id));
48786
49857
  }
48787
- var modelsCommand = defineCommand195({
49858
+ var modelsCommand = defineCommand197({
48788
49859
  meta: {
48789
49860
  name: "models",
48790
49861
  description: `${DESCRIPTION2}
@@ -48831,13 +49902,13 @@ Examples:
48831
49902
  });
48832
49903
 
48833
49904
  // src/commands/studio/skills.ts
48834
- import { defineCommand as defineCommand196 } from "citty";
49905
+ import { defineCommand as defineCommand198 } from "citty";
48835
49906
  registerSchema({
48836
49907
  command: "studio.skills",
48837
49908
  description: "The craft directions `studio generate --skill <id>` accepts. Each one carries directed art direction plus the model, shape and take count it wants, so you pick a look by name instead of writing the boilerplate yourself.",
48838
49909
  args: {}
48839
49910
  });
48840
- var skillsCommand = defineCommand196({
49911
+ var skillsCommand = defineCommand198({
48841
49912
  meta: {
48842
49913
  name: "skills",
48843
49914
  description: "List the craft directions available to `baker studio generate --skill <id>` \u2014 what each one is for, whether it wants a reference image, and the model/shape/count it defaults to.\n\nExample: baker studio skills"
@@ -48861,7 +49932,7 @@ var skillsCommand = defineCommand196({
48861
49932
  });
48862
49933
 
48863
49934
  // src/commands/studio/index.ts
48864
- var studioCommand = defineCommand197({
49935
+ var studioCommand = defineCommand199({
48865
49936
  meta: {
48866
49937
  name: "studio",
48867
49938
  description: `Make new imagery and clips. Every batch is recorded and shows up in the dashboard Studio for the client to review, labelled with this conversation.
@@ -48904,10 +49975,10 @@ Full guide: __tooling__/docs/tools/baker/studio.md`
48904
49975
  });
48905
49976
 
48906
49977
  // src/commands/tag-manager/index.ts
48907
- import { defineCommand as defineCommand201 } from "citty";
49978
+ import { defineCommand as defineCommand203 } from "citty";
48908
49979
 
48909
49980
  // src/commands/tag-manager/draft.ts
48910
- import { defineCommand as defineCommand198 } from "citty";
49981
+ import { defineCommand as defineCommand200 } from "citty";
48911
49982
 
48912
49983
  // src/commands/tag-manager/shared.ts
48913
49984
  import { readFileSync as readFileSync15 } from "fs";
@@ -48974,10 +50045,10 @@ async function stageOp4(op) {
48974
50045
  handleError5(err);
48975
50046
  }
48976
50047
  }
48977
- async function draftAction3(path40, body, chat) {
50048
+ async function draftAction3(path43, body, chat) {
48978
50049
  const chatId = resolveChatId(chat);
48979
50050
  try {
48980
- const data = await apiPost(path40, { chatId, ...body });
50051
+ const data = await apiPost(path43, { chatId, ...body });
48981
50052
  writeJsonEnvelope({ ok: true, data });
48982
50053
  return data;
48983
50054
  } catch (err) {
@@ -49030,13 +50101,13 @@ registerSchema({
49030
50101
  chat: { type: "string", description: CHAT_READ_ARG.description, required: false }
49031
50102
  }
49032
50103
  });
49033
- var draftCommand4 = defineCommand198({
50104
+ var draftCommand4 = defineCommand200({
49034
50105
  meta: {
49035
50106
  name: "draft",
49036
50107
  description: "List, show, amend, remove, or clear staged Tag Manager changes for this chat. `list` and `show` take --chat <id> to read an earlier chat's changes instead."
49037
50108
  },
49038
50109
  subCommands: {
49039
- list: defineCommand198({
50110
+ list: defineCommand200({
49040
50111
  meta: {
49041
50112
  name: "list",
49042
50113
  description: "Review everything staged on this chat (--json for the raw envelope)"
@@ -49049,7 +50120,7 @@ var draftCommand4 = defineCommand198({
49049
50120
  await draftList2(args.json === true, args.chat);
49050
50121
  }
49051
50122
  }),
49052
- show: defineCommand198({
50123
+ show: defineCommand200({
49053
50124
  meta: {
49054
50125
  name: "show",
49055
50126
  description: "Print the full staged payload for one change \u2014 the receipt to verify it looks right before publish (never truncated)."
@@ -49066,7 +50137,7 @@ var draftCommand4 = defineCommand198({
49066
50137
  );
49067
50138
  }
49068
50139
  }),
49069
- amend: defineCommand198({
50140
+ amend: defineCommand200({
49070
50141
  meta: {
49071
50142
  name: "amend",
49072
50143
  description: "Update a staged change in place \u2014 merges a JSON patch into its payload (objects deep-merge, null deletes a key, arrays/scalars replace) and re-validates. Use this instead of remove + re-create."
@@ -49083,7 +50154,7 @@ var draftCommand4 = defineCommand198({
49083
50154
  });
49084
50155
  }
49085
50156
  }),
49086
- remove: defineCommand198({
50157
+ remove: defineCommand200({
49087
50158
  meta: { name: "remove", description: "Remove one staged change (cascades to anything depending on it)" },
49088
50159
  args: { ref: { type: "positional", description: "Staged ref (gtm_temp_*) or target", required: false } },
49089
50160
  run: async ({ args }) => {
@@ -49092,7 +50163,7 @@ var draftCommand4 = defineCommand198({
49092
50163
  });
49093
50164
  }
49094
50165
  }),
49095
- clear: defineCommand198({
50166
+ clear: defineCommand200({
49096
50167
  meta: { name: "clear", description: "Discard all Tag Manager changes staged on this chat" },
49097
50168
  run: async () => {
49098
50169
  await draftAction3("/api/tag-manager/draft/clear", {});
@@ -49102,7 +50173,7 @@ var draftCommand4 = defineCommand198({
49102
50173
  });
49103
50174
 
49104
50175
  // src/commands/tag-manager/read.ts
49105
- import { defineCommand as defineCommand199 } from "citty";
50176
+ import { defineCommand as defineCommand201 } from "citty";
49106
50177
  registerSchema({
49107
50178
  command: "tagManager.containers",
49108
50179
  description: "List the Google Tag Manager containers this company's connection can reach. Every container the company connected is flagged `connected: true` \u2014 there can be several, and Baker may read and change all of them. Start here to confirm which containers you are managing.",
@@ -49143,7 +50214,7 @@ function containersHints(containers) {
49143
50214
  }))
49144
50215
  });
49145
50216
  }
49146
- var containersCommand = defineCommand199({
50217
+ var containersCommand = defineCommand201({
49147
50218
  meta: {
49148
50219
  name: "containers",
49149
50220
  description: `List Tag Manager containers reachable by this company's connection.
@@ -49160,7 +50231,7 @@ Start here:
49160
50231
  }
49161
50232
  }
49162
50233
  });
49163
- var readCommand = defineCommand199({
50234
+ var readCommand = defineCommand201({
49164
50235
  meta: {
49165
50236
  name: "read",
49166
50237
  description: `Read the current contents of the Tag Manager container \u2014 always do this before staging changes.
@@ -49202,7 +50273,7 @@ Examples:
49202
50273
  });
49203
50274
 
49204
50275
  // src/commands/tag-manager/write-commands.ts
49205
- import { defineCommand as defineCommand200 } from "citty";
50276
+ import { defineCommand as defineCommand202 } from "citty";
49206
50277
  var CONTAINER_ARG_DESCRIPTION = "Numeric container id (optional only when one container is connected \u2014 run `baker tag-manager containers`)";
49207
50278
  var ENTITIES = [
49208
50279
  {
@@ -49258,10 +50329,10 @@ for (const { entity, noun, createHint } of ENTITIES) {
49258
50329
  });
49259
50330
  }
49260
50331
  function entityCommand(entity, noun, example) {
49261
- return defineCommand200({
50332
+ return defineCommand202({
49262
50333
  meta: { name: entity, description: `Stage ${noun} changes on this chat's Tag Manager draft` },
49263
50334
  subCommands: {
49264
- create: defineCommand200({
50335
+ create: defineCommand202({
49265
50336
  meta: {
49266
50337
  name: "create",
49267
50338
  description: `Stage a new ${noun}
@@ -49283,7 +50354,7 @@ Examples:
49283
50354
  });
49284
50355
  }
49285
50356
  }),
49286
- update: defineCommand200({
50357
+ update: defineCommand202({
49287
50358
  meta: {
49288
50359
  name: "update",
49289
50360
  description: `Stage an update to an existing ${noun} (pass its id or path)`
@@ -49303,7 +50374,7 @@ Examples:
49303
50374
  });
49304
50375
  }
49305
50376
  }),
49306
- delete: defineCommand200({
50377
+ delete: defineCommand202({
49307
50378
  meta: { name: "delete", description: `Stage the deletion of a ${noun} (pass its id or path)` },
49308
50379
  args: {
49309
50380
  id: { type: "positional", description: `${noun} id or path`, required: false },
@@ -49344,7 +50415,7 @@ function builtinTypes(args) {
49344
50415
  }
49345
50416
  return raw.split(",").map((entry) => entry.trim());
49346
50417
  }
49347
- var builtinCommand = defineCommand200({
50418
+ var builtinCommand = defineCommand202({
49348
50419
  meta: {
49349
50420
  name: "builtin",
49350
50421
  description: `Enable or disable built-in variables
@@ -49354,7 +50425,7 @@ Examples:
49354
50425
  baker tag-manager builtin disable --types formId`
49355
50426
  },
49356
50427
  subCommands: {
49357
- enable: defineCommand200({
50428
+ enable: defineCommand202({
49358
50429
  meta: { name: "enable", description: "Stage enabling built-in variables" },
49359
50430
  args: {
49360
50431
  types: { type: "string", description: "Comma-separated types", required: false },
@@ -49368,7 +50439,7 @@ Examples:
49368
50439
  });
49369
50440
  }
49370
50441
  }),
49371
- disable: defineCommand200({
50442
+ disable: defineCommand202({
49372
50443
  meta: { name: "disable", description: "Stage disabling built-in variables" },
49373
50444
  args: {
49374
50445
  types: { type: "string", description: "Comma-separated types", required: false },
@@ -49386,7 +50457,7 @@ Examples:
49386
50457
  });
49387
50458
 
49388
50459
  // src/commands/tag-manager/index.ts
49389
- var tagManagerCommand = defineCommand201({
50460
+ var tagManagerCommand = defineCommand203({
49390
50461
  meta: {
49391
50462
  name: "tag-manager",
49392
50463
  description: `Read and change what lives inside the client's Google Tag Manager container \u2014 tags, triggers, variables, folders and built-in variables.
@@ -49423,7 +50494,7 @@ Full guide: __tooling__/docs/tools/baker/tag-manager.md`
49423
50494
  });
49424
50495
 
49425
50496
  // src/commands/tags/index.ts
49426
- import { defineCommand as defineCommand202 } from "citty";
50497
+ import { defineCommand as defineCommand204 } from "citty";
49427
50498
 
49428
50499
  // src/commands/tags/shared.ts
49429
50500
  function failApi3(err) {
@@ -49492,7 +50563,7 @@ async function listTags(json) {
49492
50563
  var listArgs9 = {
49493
50564
  json: { type: "boolean", description: "Print the raw JSON envelope instead of the readable list" }
49494
50565
  };
49495
- var listCommand17 = defineCommand202({
50566
+ var listCommand17 = defineCommand204({
49496
50567
  meta: {
49497
50568
  name: "list",
49498
50569
  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"
@@ -49511,7 +50582,7 @@ async function listDraft3(chat) {
49511
50582
  failApi3(err);
49512
50583
  }
49513
50584
  }
49514
- var draftCommand5 = defineCommand202({
50585
+ var draftCommand5 = defineCommand204({
49515
50586
  meta: {
49516
50587
  name: "draft",
49517
50588
  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). Takes --chat <id> to read an earlier chat's staged changes instead."
@@ -49521,7 +50592,7 @@ var draftCommand5 = defineCommand202({
49521
50592
  await listDraft3(args.chat);
49522
50593
  }
49523
50594
  });
49524
- var tagsCommand4 = defineCommand202({
50595
+ var tagsCommand4 = defineCommand204({
49525
50596
  meta: {
49526
50597
  name: "tags",
49527
50598
  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.
@@ -49550,10 +50621,10 @@ Full guide: __tooling__/docs/tools/baker/tags.md`
49550
50621
  });
49551
50622
 
49552
50623
  // src/commands/testimonials/index.ts
49553
- import { defineCommand as defineCommand206 } from "citty";
50624
+ import { defineCommand as defineCommand208 } from "citty";
49554
50625
 
49555
50626
  // src/commands/testimonials/get.ts
49556
- import { defineCommand as defineCommand203 } from "citty";
50627
+ import { defineCommand as defineCommand205 } from "citty";
49557
50628
  registerSchema({
49558
50629
  command: "testimonials.get",
49559
50630
  description: "Get a single testimonial by ID",
@@ -49561,7 +50632,7 @@ registerSchema({
49561
50632
  id: { type: "string", description: "Testimonial ID", required: true }
49562
50633
  }
49563
50634
  });
49564
- var getCommand6 = defineCommand203({
50635
+ var getCommand6 = defineCommand205({
49565
50636
  meta: { name: "get", description: "Get a single testimonial by ID. Example: baker testimonials get j571abc123" },
49566
50637
  args: {
49567
50638
  id: { type: "positional", description: "Testimonial ID", required: false },
@@ -49598,7 +50669,7 @@ var getCommand6 = defineCommand203({
49598
50669
  });
49599
50670
 
49600
50671
  // src/commands/testimonials/list.ts
49601
- import { defineCommand as defineCommand204 } from "citty";
50672
+ import { defineCommand as defineCommand206 } from "citty";
49602
50673
 
49603
50674
  // src/commands/testimonials/emptyCorpusHints.ts
49604
50675
  function resolveEmptyReason({
@@ -49733,7 +50804,7 @@ function buildListParams(args) {
49733
50804
  }
49734
50805
  return params;
49735
50806
  }
49736
- var listCommand18 = defineCommand204({
50807
+ var listCommand18 = defineCommand206({
49737
50808
  meta: {
49738
50809
  name: "list",
49739
50810
  description: "List testimonials with optional filters. Example: baker testimonials list --source google --sentiment positive"
@@ -49785,7 +50856,7 @@ var listCommand18 = defineCommand204({
49785
50856
  });
49786
50857
 
49787
50858
  // src/commands/testimonials/search.ts
49788
- import { defineCommand as defineCommand205 } from "citty";
50859
+ import { defineCommand as defineCommand207 } from "citty";
49789
50860
  var FILTER_FLAGS2 = ["source", "rating-min", "rating-max", "status", "sentiment", "language", "tags"];
49790
50861
  function languageBiasHint(results, requestedLanguage) {
49791
50862
  if (requestedLanguage) {
@@ -49864,7 +50935,7 @@ function buildSearchRequest(query, args) {
49864
50935
  }
49865
50936
  return body;
49866
50937
  }
49867
- var searchCommand3 = defineCommand205({
50938
+ var searchCommand3 = defineCommand207({
49868
50939
  meta: {
49869
50940
  name: "search",
49870
50941
  description: "Semantic search testimonials by text query. Uses hybrid BM25 + vector + reranking. Example: baker testimonials search 'great service' --rating-min 4"
@@ -49928,7 +50999,7 @@ var searchCommand3 = defineCommand205({
49928
50999
  var tagsCommand5 = makeTagsCommand("testimonials", "testimonial", "/api/testimonials/tags");
49929
51000
 
49930
51001
  // src/commands/testimonials/index.ts
49931
- var testimonialsCommand = defineCommand206({
51002
+ var testimonialsCommand = defineCommand208({
49932
51003
  meta: {
49933
51004
  name: "testimonials",
49934
51005
  description: `Find and browse testimonials in Baker. Subcommands: search, get, list, tags.
@@ -49950,10 +51021,10 @@ Full guide: __tooling__/docs/tools/baker/testimonials.md`
49950
51021
  });
49951
51022
 
49952
51023
  // src/commands/videos/index.ts
49953
- import { defineCommand as defineCommand213 } from "citty";
51024
+ import { defineCommand as defineCommand215 } from "citty";
49954
51025
 
49955
51026
  // src/commands/videos/delete.ts
49956
- import { defineCommand as defineCommand207 } from "citty";
51027
+ import { defineCommand as defineCommand209 } from "citty";
49957
51028
  registerSchema({
49958
51029
  command: "videos.delete",
49959
51030
  description: "Delete a video by ID",
@@ -49967,7 +51038,7 @@ registerSchema({
49967
51038
  }
49968
51039
  }
49969
51040
  });
49970
- var deleteCommand4 = defineCommand207({
51041
+ var deleteCommand4 = defineCommand209({
49971
51042
  meta: {
49972
51043
  name: "delete",
49973
51044
  description: "Delete a video by ID. Use --dry-run to preview. Example: baker videos delete j571abc123 --dry-run"
@@ -50008,7 +51079,7 @@ var deleteCommand4 = defineCommand207({
50008
51079
  });
50009
51080
 
50010
51081
  // src/commands/videos/get.ts
50011
- import { defineCommand as defineCommand208 } from "citty";
51082
+ import { defineCommand as defineCommand210 } from "citty";
50012
51083
  registerSchema({
50013
51084
  command: "videos.get",
50014
51085
  description: "Get a single video by ID",
@@ -50016,7 +51087,7 @@ registerSchema({
50016
51087
  id: { type: "string", description: "Video ID", required: true }
50017
51088
  }
50018
51089
  });
50019
- var getCommand7 = defineCommand208({
51090
+ var getCommand7 = defineCommand210({
50020
51091
  meta: { name: "get", description: "Get a single video by ID. Example: baker videos get j571abc123" },
50021
51092
  args: {
50022
51093
  id: { type: "positional", description: "Video ID", required: false },
@@ -50053,7 +51124,7 @@ var getCommand7 = defineCommand208({
50053
51124
  });
50054
51125
 
50055
51126
  // src/commands/videos/group.ts
50056
- import { defineCommand as defineCommand209 } from "citty";
51127
+ import { defineCommand as defineCommand211 } from "citty";
50057
51128
  registerSchema({
50058
51129
  command: "videos.group",
50059
51130
  description: "List every clip and image that arrived in the same set as this video (carousel slides, one page)",
@@ -50062,7 +51133,7 @@ registerSchema({
50062
51133
  "group-key": { type: "string", description: "The set key directly, when you already have it", required: false }
50063
51134
  }
50064
51135
  });
50065
- var groupCommand2 = defineCommand209({
51136
+ var groupCommand2 = defineCommand211({
50066
51137
  meta: {
50067
51138
  name: "group",
50068
51139
  description: "List every asset that arrived in the same set as this clip \u2014 the other slides of the Instagram post it came from, stills included. A carousel is authored to be read in order, so a clip pulled out of one is usually missing half its meaning. Example: baker videos group <videoId>"
@@ -50082,10 +51153,10 @@ var groupCommand2 = defineCommand209({
50082
51153
  });
50083
51154
 
50084
51155
  // src/commands/videos/ingest.ts
50085
- import { mkdtemp as mkdtemp2, rm as rm7, stat as stat7 } from "fs/promises";
51156
+ import { mkdtemp as mkdtemp2, rm as rm7, stat as stat8 } from "fs/promises";
50086
51157
  import { tmpdir as tmpdir3 } from "os";
50087
- import path39 from "path";
50088
- import { defineCommand as defineCommand210 } from "citty";
51158
+ import path42 from "path";
51159
+ import { defineCommand as defineCommand212 } from "citty";
50089
51160
 
50090
51161
  // src/lib/streamUpload.ts
50091
51162
  import { createHash as createHash2 } from "crypto";
@@ -50249,7 +51320,7 @@ registerSchema({
50249
51320
  "dry-run": { type: "boolean", description: "Preview the operation without executing", required: false }
50250
51321
  }
50251
51322
  });
50252
- var ingestCommand2 = defineCommand210({
51323
+ var ingestCommand2 = defineCommand212({
50253
51324
  meta: {
50254
51325
  name: "ingest",
50255
51326
  description: "Add a video to the library from a URL. A direct file URL is handed straight to Baker, which fetches it. A page URL (YouTube, TikTok, Vimeo, Instagram) is downloaded here first, then uploaded \u2014 and a direct URL that Baker cannot fetch falls back to that same path automatically.\n\nExample: baker videos ingest https://www.youtube.com/watch?v=abc123"
@@ -50365,35 +51436,35 @@ async function ingestByRoute(args, direct, country) {
50365
51436
  function reportYtDlpFailure(err) {
50366
51437
  const detail = `${err.stderrTail} ${err.message}`;
50367
51438
  if (isProxyFailure(ytDlpBlockSignal(detail))) {
50368
- fail7(
51439
+ fail9(
50369
51440
  "Couldn't download that video: our connection to the internet was refused.",
50370
51441
  "This is Baker's egress proxy, not the link \u2014 its credentials look wrong or expired. The link is probably fine. Report it; retrying won't help until the proxy is fixed."
50371
51442
  );
50372
51443
  }
50373
51444
  if (isDrmProtected(detail)) {
50374
- fail7(
51445
+ fail9(
50375
51446
  "That video is copy-protected, so it can't be added to the library.",
50376
51447
  "The publisher encrypted this one against downloading. Nothing will change that \u2014 not retrying, not a different region. Ask whoever owns the video for the original file and add it with `baker videos upload <file>`."
50377
51448
  );
50378
51449
  }
50379
51450
  if (isVimeoAuthFailure(detail)) {
50380
- fail7(
51451
+ fail9(
50381
51452
  "Vimeo wouldn't hand over that video \u2014 Baker isn't signed in to Vimeo.",
50382
51453
  "Vimeo now refuses anonymous downloads entirely, so this needs a signed-in session and the one Baker holds has either expired or was never set. Nothing is wrong with the link. Report it so the Vimeo cookie can be refreshed; meanwhile download the file and use `baker videos upload <file>`."
50383
51454
  );
50384
51455
  }
50385
51456
  if (isRegionBlocked(detail)) {
50386
- fail7(
51457
+ fail9(
50387
51458
  `That video isn't available in the region we're fetching from. ${err.stderrTail || err.message}`,
50388
51459
  "Re-run with `--country <two-letter code>` for a country where the video plays \u2014 e.g. `--country US`. Pick the one the video belongs to (the uploader's country is the usual answer); each attempt costs a paid connection, so choose rather than work through the list."
50389
51460
  );
50390
51461
  }
50391
- fail7(
51462
+ fail9(
50392
51463
  `Couldn't download that video. ${err.stderrTail || err.message}`,
50393
51464
  isToolingOrBlockFailure(detail) ? "The site wouldn't hand over the video \u2014 usually the downloader being out of date against a site that changed, or the request being blocked. Retrying won't help, and the link is probably fine. Report it so the tool can be updated; meanwhile download the file and use `baker videos upload <file>`." : "Check the link is public and playable. Private or age-restricted videos can't be downloaded \u2014 download the file yourself and use `baker videos upload <file>`."
50394
51465
  );
50395
51466
  }
50396
- function fail7(message, fix, code = "INGEST_FAILED") {
51467
+ function fail9(message, fix, code = "INGEST_FAILED") {
50397
51468
  writeJson({ ok: false, error: { code, message, fix } });
50398
51469
  process.exit(1);
50399
51470
  }
@@ -50405,13 +51476,13 @@ function reportIngestFailure(err) {
50405
51476
  if (err instanceof YtDlpError) reportYtDlpFailure(err);
50406
51477
  const detail = err instanceof Error ? err.message : String(err);
50407
51478
  if (detail.includes("timed out after")) {
50408
- fail7(
51479
+ fail9(
50409
51480
  "That video took too long to download and was stopped part-way.",
50410
51481
  "Long or slow-serving videos can outrun the download window. Try a shorter clip, or download this one yourself and use `baker videos upload <file>`.",
50411
51482
  "INTERNAL_ERROR"
50412
51483
  );
50413
51484
  }
50414
- fail7(
51485
+ fail9(
50415
51486
  `Couldn't add that video. ${detail}`,
50416
51487
  "This is a fault on Baker's side, not a problem with the link. Report it.",
50417
51488
  "INTERNAL_ERROR"
@@ -50435,7 +51506,7 @@ function ingestUrl(args) {
50435
51506
  }
50436
51507
  async function downloadThenIngest(args, country) {
50437
51508
  const vimeoCookie = captureVimeoCookie();
50438
- const workDir = await mkdtemp2(path39.join(tmpdir3(), "videos-ingest-"));
51509
+ const workDir = await mkdtemp2(path42.join(tmpdir3(), "videos-ingest-"));
50439
51510
  try {
50440
51511
  const probe = await probeYtDlp({ url: args.url, country, vimeoCookie, cookieDir: workDir });
50441
51512
  if (isAudioOnly(probe.info)) {
@@ -50466,7 +51537,7 @@ async function downloadThenIngest(args, country) {
50466
51537
  // we allow to fetch it cannot drift apart.
50467
51538
  timeoutMs: downloadTimeoutMs(durationSeconds(probe.info))
50468
51539
  });
50469
- const stats = await stat7(filePath);
51540
+ const stats = await stat8(filePath);
50470
51541
  if (stats.size > MAX_VIDEO_INGEST_BYTES) {
50471
51542
  throw new ApiError(
50472
51543
  "VALIDATION_ERROR",
@@ -50511,7 +51582,7 @@ async function uploadToAssetStore(filePath, sizeBytes) {
50511
51582
  }
50512
51583
 
50513
51584
  // src/commands/videos/search.ts
50514
- import { defineCommand as defineCommand211 } from "citty";
51585
+ import { defineCommand as defineCommand213 } from "citty";
50515
51586
  registerSchema({
50516
51587
  command: "videos.search",
50517
51588
  description: "Search videos by text query. Only returns ready videos.",
@@ -50521,7 +51592,7 @@ registerSchema({
50521
51592
  tags: { type: "string", description: "Comma-separated tags to filter by", required: false }
50522
51593
  }
50523
51594
  });
50524
- var searchCommand4 = defineCommand211({
51595
+ var searchCommand4 = defineCommand213({
50525
51596
  meta: {
50526
51597
  name: "search",
50527
51598
  description: "Semantic search videos by text query. Uses hybrid BM25 + vector + reranking. Example: baker videos search 'product demo' --tags tutorial"
@@ -50571,9 +51642,9 @@ var searchCommand4 = defineCommand211({
50571
51642
  var tagsCommand6 = makeTagsCommand("videos", "video", "/api/videos/tags");
50572
51643
 
50573
51644
  // src/commands/videos/upload.ts
50574
- import { readFile as readFile25, stat as stat8 } from "fs/promises";
51645
+ import { readFile as readFile27, stat as stat9 } from "fs/promises";
50575
51646
  import { basename as basename3, extname as extname4 } from "path";
50576
- import { defineCommand as defineCommand212 } from "citty";
51647
+ import { defineCommand as defineCommand214 } from "citty";
50577
51648
  var MIME_MAP = {
50578
51649
  ".mp4": "video/mp4",
50579
51650
  ".mov": "video/quicktime",
@@ -50615,7 +51686,7 @@ function detectContentType(filePath) {
50615
51686
  function isRemoteUrl3(value) {
50616
51687
  return /^https?:\/\//i.test(value);
50617
51688
  }
50618
- var uploadCommand2 = defineCommand212({
51689
+ var uploadCommand2 = defineCommand214({
50619
51690
  meta: {
50620
51691
  name: "upload",
50621
51692
  description: "Upload a video to Baker \u2014 accepts a local file path OR a remote http(s) URL.\n\nLocal: auto-detects content type and uploads via Mux direct upload.\nRemote: hands off to `videos ingest` (direct fetch, or download-then-upload for a YouTube/TikTok/Vimeo page).\n\nExamples:\n baker videos upload ./demo.mp4\n baker videos upload https://www.youtube.com/watch?v=abc123"
@@ -50655,7 +51726,7 @@ var uploadCommand2 = defineCommand212({
50655
51726
  const originalFilename = basename3(filePath);
50656
51727
  const descriptionContext = args.context;
50657
51728
  if (args["dry-run"]) {
50658
- const fileStats = await stat8(filePath);
51729
+ const fileStats = await stat9(filePath);
50659
51730
  writeJson({
50660
51731
  ok: true,
50661
51732
  dryRun: true,
@@ -50668,7 +51739,7 @@ var uploadCommand2 = defineCommand212({
50668
51739
  originalFilename,
50669
51740
  descriptionContext
50670
51741
  });
50671
- const fileBuffer = await readFile25(filePath);
51742
+ const fileBuffer = await readFile27(filePath);
50672
51743
  const uploadResponse = await fetch(uploadUrl, {
50673
51744
  method: "PUT",
50674
51745
  headers: { "Content-Type": contentType },
@@ -50699,7 +51770,7 @@ var uploadCommand2 = defineCommand212({
50699
51770
  });
50700
51771
 
50701
51772
  // src/commands/videos/index.ts
50702
- var videosCommand = defineCommand213({
51773
+ var videosCommand = defineCommand215({
50703
51774
  meta: {
50704
51775
  name: "videos",
50705
51776
  description: `Find and manage videos in Baker. Subcommands: search, get, upload, ingest, delete, tags.
@@ -50726,10 +51797,10 @@ Full guide: __tooling__/docs/tools/baker/videos.md`
50726
51797
  });
50727
51798
 
50728
51799
  // src/commands/winning-ads/index.ts
50729
- import { defineCommand as defineCommand226 } from "citty";
51800
+ import { defineCommand as defineCommand228 } from "citty";
50730
51801
 
50731
51802
  // src/commands/winning-ads/advertisers.ts
50732
- import { defineCommand as defineCommand214 } from "citty";
51803
+ import { defineCommand as defineCommand216 } from "citty";
50733
51804
 
50734
51805
  // src/commands/winning-ads/shared.ts
50735
51806
  function splitList2(value) {
@@ -50782,7 +51853,7 @@ function advertiserNormalizer(record, full) {
50782
51853
  last_synced_at: record.last_synced_at ?? null
50783
51854
  };
50784
51855
  }
50785
- var advertisersCommand2 = defineCommand214({
51856
+ var advertisersCommand2 = defineCommand216({
50786
51857
  meta: {
50787
51858
  name: "advertisers",
50788
51859
  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'
@@ -50840,7 +51911,7 @@ var advertisersCommand2 = defineCommand214({
50840
51911
  });
50841
51912
 
50842
51913
  // src/commands/winning-ads/brief.ts
50843
- import { defineCommand as defineCommand215 } from "citty";
51914
+ import { defineCommand as defineCommand217 } from "citty";
50844
51915
  registerSchema({
50845
51916
  command: "winning-ads.brief",
50846
51917
  description: "Generate a creative brief grounded in strategically-similar winning ads. Optionally describe the target creative with --dna (JSON) and steer with --notes.",
@@ -50886,7 +51957,7 @@ function parseDna(raw) {
50886
51957
  }
50887
51958
  return parsed;
50888
51959
  }
50889
- var briefCommand = defineCommand215({
51960
+ var briefCommand = defineCommand217({
50890
51961
  meta: {
50891
51962
  name: "brief",
50892
51963
  description: `Generate a creative brief from winning references. Example: baker winning-ads brief --dna '{"angle":"cost savings"}' --notes "B2B, LinkedIn video" --k 8`
@@ -50922,7 +51993,7 @@ var briefCommand = defineCommand215({
50922
51993
  });
50923
51994
 
50924
51995
  // src/commands/winning-ads/content.ts
50925
- import { defineCommand as defineCommand216 } from "citty";
51996
+ import { defineCommand as defineCommand218 } from "citty";
50926
51997
  registerSchema({
50927
51998
  command: "winning-ads.content",
50928
51999
  description: "Read what's INSIDE one winning ad: the spoken transcript, the on-screen text, and the ad copy. Use this after `search`/`winners`/`feed` return a shortlist \u2014 pass an ad_id to understand a reference before reproducing it. Add --full for speech, pacing, and soundtrack detail. Video ads carry the transcript/on-screen text; static ads carry only the copy.",
@@ -50935,7 +52006,7 @@ registerSchema({
50935
52006
  }
50936
52007
  }
50937
52008
  });
50938
- var contentCommand = defineCommand216({
52009
+ var contentCommand = defineCommand218({
50939
52010
  meta: {
50940
52011
  name: "content",
50941
52012
  description: "Read the transcript + on-screen text + copy of one winning ad. Example: baker winning-ads content adg_123 --platform meta --full --output md"
@@ -50984,7 +52055,7 @@ var contentCommand = defineCommand216({
50984
52055
  });
50985
52056
 
50986
52057
  // src/commands/winning-ads/feed.ts
50987
- import { defineCommand as defineCommand217 } from "citty";
52058
+ import { defineCommand as defineCommand219 } from "citty";
50988
52059
  function buildFeedParams(input) {
50989
52060
  const params = {};
50990
52061
  const advertiser = splitList2(input.advertiser);
@@ -51036,7 +52107,7 @@ registerSchema({
51036
52107
  format: { type: "string", description: "Comma-separated formats to include (e.g. static,video)", required: false }
51037
52108
  }
51038
52109
  });
51039
- var feedCommand = defineCommand217({
52110
+ var feedCommand = defineCommand219({
51040
52111
  meta: {
51041
52112
  name: "feed",
51042
52113
  description: "Winners across every brand you follow (browse, then trim per advertiser). Example: baker winning-ads feed --per-advertiser 5 --output md"
@@ -51121,7 +52192,7 @@ var feedCommand = defineCommand217({
51121
52192
  });
51122
52193
 
51123
52194
  // src/commands/winning-ads/follow.ts
51124
- import { defineCommand as defineCommand218 } from "citty";
52195
+ import { defineCommand as defineCommand220 } from "citty";
51125
52196
  var PLATFORMS = ["meta", "linkedin"];
51126
52197
  registerSchema({
51127
52198
  command: "winning-ads.follow",
@@ -51136,7 +52207,7 @@ registerSchema({
51136
52207
  label: { type: "string", description: "Optional display label (defaults to the resolved name)", required: false }
51137
52208
  }
51138
52209
  });
51139
- var followCommand = defineCommand218({
52210
+ var followCommand = defineCommand220({
51140
52211
  meta: {
51141
52212
  name: "follow",
51142
52213
  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'
@@ -51183,7 +52254,7 @@ var followCommand = defineCommand218({
51183
52254
  });
51184
52255
 
51185
52256
  // src/commands/winning-ads/follow-competitors.ts
51186
- import { defineCommand as defineCommand219 } from "citty";
52257
+ import { defineCommand as defineCommand221 } from "citty";
51187
52258
  var PLATFORMS2 = ["meta", "linkedin"];
51188
52259
  var BATCH_TIMEOUT_MS = 3e5;
51189
52260
  function buildFollowBatchBody(input) {
@@ -51216,7 +52287,7 @@ registerSchema({
51216
52287
  }
51217
52288
  }
51218
52289
  });
51219
- var followCompetitorsCommand = defineCommand219({
52290
+ var followCompetitorsCommand = defineCommand221({
51220
52291
  meta: {
51221
52292
  name: "follow-competitors",
51222
52293
  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"'
@@ -51291,7 +52362,7 @@ var followCompetitorsCommand = defineCommand219({
51291
52362
  });
51292
52363
 
51293
52364
  // src/commands/winning-ads/following.ts
51294
- import { defineCommand as defineCommand220 } from "citty";
52365
+ import { defineCommand as defineCommand222 } from "citty";
51295
52366
  registerSchema({
51296
52367
  command: "winning-ads.following",
51297
52368
  description: "List the brands you follow in your ad-dna library, with each one's status (ready vs still adding) and cached ad counts. A brand still adding has counts that are a lie in progress; one with `discovery_failed` has counts that are short because we couldn't finish looking, which is not the same as it running no ads.",
@@ -51345,7 +52416,7 @@ function followingNormalizer(record, full) {
51345
52416
  platforms: Array.isArray(record.platforms) ? record.platforms : []
51346
52417
  };
51347
52418
  }
51348
- var followingCommand = defineCommand220({
52419
+ var followingCommand = defineCommand222({
51349
52420
  meta: {
51350
52421
  name: "following",
51351
52422
  description: "List brands you follow, with status (ready / adding\u2026) and cached counts. A brand's counts are only final once it is ready. Example: baker winning-ads following --output md"
@@ -51381,7 +52452,7 @@ var followingCommand = defineCommand220({
51381
52452
  });
51382
52453
 
51383
52454
  // src/commands/winning-ads/patterns.ts
51384
- import { defineCommand as defineCommand221 } from "citty";
52455
+ import { defineCommand as defineCommand223 } from "citty";
51385
52456
  registerSchema({
51386
52457
  command: "winning-ads.patterns",
51387
52458
  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.",
@@ -51420,7 +52491,7 @@ function discriminatorRow(record) {
51420
52491
  top_values_duds: Array.isArray(record.top_values_b) ? record.top_values_b.join(", ") : ""
51421
52492
  };
51422
52493
  }
51423
- var patternsCommand = defineCommand221({
52494
+ var patternsCommand = defineCommand223({
51424
52495
  meta: {
51425
52496
  name: "patterns",
51426
52497
  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"
@@ -51476,7 +52547,7 @@ var patternsCommand = defineCommand221({
51476
52547
  });
51477
52548
 
51478
52549
  // src/commands/winning-ads/search.ts
51479
- import { defineCommand as defineCommand222 } from "citty";
52550
+ import { defineCommand as defineCommand224 } from "citty";
51480
52551
  registerSchema({
51481
52552
  command: "winning-ads.search",
51482
52553
  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.",
@@ -51584,7 +52655,7 @@ function buildSearchBody2(args) {
51584
52655
  }
51585
52656
  return body;
51586
52657
  }
51587
- var searchCommand5 = defineCommand222({
52658
+ var searchCommand5 = defineCommand224({
51588
52659
  meta: {
51589
52660
  name: "search",
51590
52661
  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"
@@ -51699,7 +52770,7 @@ var searchCommand5 = defineCommand222({
51699
52770
  });
51700
52771
 
51701
52772
  // src/commands/winning-ads/seeds.ts
51702
- import { defineCommand as defineCommand223 } from "citty";
52773
+ import { defineCommand as defineCommand225 } from "citty";
51703
52774
  function leanRow(r) {
51704
52775
  return {
51705
52776
  key: r.key,
@@ -51727,7 +52798,7 @@ function makeSeedCommand(opts) {
51727
52798
  limit: { type: "number", description: "Max keys 1-100 (default 20)", required: false, default: 20 }
51728
52799
  }
51729
52800
  });
51730
- return defineCommand223({
52801
+ return defineCommand225({
51731
52802
  meta: { name: opts.name, description: opts.description },
51732
52803
  args: {
51733
52804
  platform: { type: "string", description: "Single platform to segment on", required: false },
@@ -51776,7 +52847,7 @@ var formatsCommand = makeSeedCommand({
51776
52847
  });
51777
52848
 
51778
52849
  // src/commands/winning-ads/unfollow.ts
51779
- import { defineCommand as defineCommand224 } from "citty";
52850
+ import { defineCommand as defineCommand226 } from "citty";
51780
52851
  registerSchema({
51781
52852
  command: "winning-ads.unfollow",
51782
52853
  description: "Stop following a brand \u2014 removes it from your ad-dna library by advertiser id.",
@@ -51784,7 +52855,7 @@ registerSchema({
51784
52855
  advertiser: { type: "string", description: "Advertiser id to unfollow", required: true }
51785
52856
  }
51786
52857
  });
51787
- var unfollowCommand = defineCommand224({
52858
+ var unfollowCommand = defineCommand226({
51788
52859
  meta: {
51789
52860
  name: "unfollow",
51790
52861
  description: "Stop following a brand by advertiser id. Example: baker winning-ads unfollow adv_123"
@@ -51805,7 +52876,7 @@ var unfollowCommand = defineCommand224({
51805
52876
  });
51806
52877
 
51807
52878
  // src/commands/winning-ads/winners.ts
51808
- import { defineCommand as defineCommand225 } from "citty";
52879
+ import { defineCommand as defineCommand227 } from "citty";
51809
52880
  registerSchema({
51810
52881
  command: "winning-ads.winners",
51811
52882
  description: "Top winning ads for one advertiser id (from `advertisers` or `following`). Returns lean winner cards; add --full for DNA + longevity.",
@@ -51815,7 +52886,7 @@ registerSchema({
51815
52886
  platform: { type: "string", description: "Filter to a single platform: meta|linkedin", required: false }
51816
52887
  }
51817
52888
  });
51818
- var winnersCommand = defineCommand225({
52889
+ var winnersCommand = defineCommand227({
51819
52890
  meta: {
51820
52891
  name: "winners",
51821
52892
  description: "Top winning ads for a specific advertiser id. Example: baker winning-ads winners adv_123 --top 15 --output md"
@@ -51865,7 +52936,7 @@ var winnersCommand = defineCommand225({
51865
52936
  });
51866
52937
 
51867
52938
  // src/commands/winning-ads/index.ts
51868
- var winningAdsCommand = defineCommand226({
52939
+ var winningAdsCommand = defineCommand228({
51869
52940
  meta: {
51870
52941
  name: "winning-ads",
51871
52942
  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.
@@ -52055,7 +53126,7 @@ function unknownFlagEnvelope(unknown, commandPath, suggestion) {
52055
53126
  };
52056
53127
  }
52057
53128
  function commandPathOf(root, argv) {
52058
- const path40 = [];
53129
+ const path43 = [];
52059
53130
  let command = root;
52060
53131
  for (const token of argv) {
52061
53132
  if (token === "--" || token.startsWith("-")) {
@@ -52066,10 +53137,10 @@ function commandPathOf(root, argv) {
52066
53137
  if (next === void 0 || typeof next !== "object") {
52067
53138
  break;
52068
53139
  }
52069
- path40.push(token);
53140
+ path43.push(token);
52070
53141
  command = next;
52071
53142
  }
52072
- return path40.join(" ");
53143
+ return path43.join(" ");
52073
53144
  }
52074
53145
  function refuseUnknownFlags(root, argv) {
52075
53146
  const unknown = findUnknownFlags(root, argv);
@@ -52103,7 +53174,7 @@ function getCliVersion() {
52103
53174
  }
52104
53175
 
52105
53176
  // src/cli.ts
52106
- var main = defineCommand227({
53177
+ var main = defineCommand229({
52107
53178
  meta: {
52108
53179
  name: "baker",
52109
53180
  version: getCliVersion(),
@@ -52122,6 +53193,7 @@ Introspection: Run 'baker schema <command>' to inspect argument schemas.`
52122
53193
  ads: adsCommand2,
52123
53194
  brand: brandCommand,
52124
53195
  analytics: analyticsCommand2,
53196
+ experiment: experimentCommand,
52125
53197
  ga4: ga4Command,
52126
53198
  gsc: gscCommand,
52127
53199
  research: researchCommand,