@koda-sl/baker-cli 0.410.0 → 0.412.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -97,7 +97,7 @@ import {
97
97
  withoutCastName,
98
98
  wordCount,
99
99
  ytDlpBlockSignal
100
- } from "./chunk-KTMFHBN6.js";
100
+ } from "./chunk-4Q7EDLEC.js";
101
101
  import {
102
102
  NAME_CONTAINS_ARG,
103
103
  csvOrJson,
@@ -108,7 +108,7 @@ import {
108
108
  resolveEffectiveStatus,
109
109
  statusFilterHints,
110
110
  todayIso
111
- } from "./chunk-ZIOW64BL.js";
111
+ } from "./chunk-PLH5K6RI.js";
112
112
  import {
113
113
  buildQueryCacheKey,
114
114
  cacheGet,
@@ -124,19 +124,19 @@ import {
124
124
  writeAdsJson,
125
125
  writeAdsOutput,
126
126
  writeJsonEnvelope
127
- } from "./chunk-VKWHVMMD.js";
127
+ } from "./chunk-G7YB37RQ.js";
128
128
  import {
129
129
  ApiError,
130
130
  apiGet,
131
131
  apiGetWithHeaders,
132
132
  apiPost,
133
133
  validateConvexId
134
- } from "./chunk-S5BY45H7.js";
134
+ } from "./chunk-FLERTMEW.js";
135
135
  import {
136
136
  installExitRecord,
137
137
  installStreamTaps,
138
138
  logInvocation
139
- } from "./chunk-ISJOVAJP.js";
139
+ } from "./chunk-EXFQDO6T.js";
140
140
  import {
141
141
  __commonJS,
142
142
  __esm,
@@ -171,7 +171,7 @@ import {
171
171
  winningAdNormalizer,
172
172
  writeJson,
173
173
  writeOutput
174
- } from "./chunk-ISCXDKWB.js";
174
+ } from "./chunk-ASON7WRD.js";
175
175
 
176
176
  // ../../node_modules/.pnpm/i18n-iso-countries@7.14.0/node_modules/i18n-iso-countries/langs/af.json
177
177
  var require_af = __commonJS({
@@ -21715,7 +21715,7 @@ var require_entry_node = __commonJS({
21715
21715
  });
21716
21716
 
21717
21717
  // src/cli.ts
21718
- import { defineCommand as defineCommand278, runMain } from "citty";
21718
+ import { defineCommand as defineCommand279, runMain } from "citty";
21719
21719
 
21720
21720
  // src/cache-flag.ts
21721
21721
  var NO_CACHE_ARG = {
@@ -23949,20 +23949,20 @@ var conversionUploadRowSchema = z4.strictObject({
23949
23949
  value: z4.number().nonnegative().optional(),
23950
23950
  currency: z4.string().regex(/^[A-Z]{3}$/, "expected a 3-letter currency code, e.g. EUR").optional()
23951
23951
  }).refine(
23952
- (row) => Boolean(row.gclid ?? row.gbraid ?? row.wbraid ?? row.email ?? row.phone ?? row.address),
23952
+ (row2) => Boolean(row2.gclid ?? row2.gbraid ?? row2.wbraid ?? row2.email ?? row2.phone ?? row2.address),
23953
23953
  "every conversion needs something to match on: a click id (gclid / gbraid / wbraid), or a customer's email, phone or name+address"
23954
23954
  ).refine(
23955
- (row) => [row.gclid, row.gbraid, row.wbraid].filter(Boolean).length <= 1,
23955
+ (row2) => [row2.gclid, row2.gbraid, row2.wbraid].filter(Boolean).length <= 1,
23956
23956
  "pass at most one click id per conversion \u2014 gclid, gbraid and wbraid are three ways of naming the same click"
23957
23957
  ).refine(
23958
- (row) => row.value === void 0 || row.currency !== void 0,
23958
+ (row2) => row2.value === void 0 || row2.currency !== void 0,
23959
23959
  "a conversion with a value needs its currency too"
23960
- ).refine((row) => row.currency === void 0 || row.value !== void 0, "currency was given with no value");
23960
+ ).refine((row2) => row2.currency === void 0 || row2.value !== void 0, "currency was given with no value");
23961
23961
  var conversionUploadSchema = z4.strictObject({
23962
23962
  conversionAction: conversionUploadActionSchema,
23963
23963
  conversions: z4.array(conversionUploadRowSchema).min(1).max(CONVERSION_UPLOAD_MAX_ROWS)
23964
23964
  }).refine((payload) => {
23965
- const ids2 = payload.conversions.map((row) => row.orderId);
23965
+ const ids2 = payload.conversions.map((row2) => row2.orderId);
23966
23966
  return new Set(ids2).size === ids2.length;
23967
23967
  }, "two conversions share an order id \u2014 each order id can appear once per upload");
23968
23968
  var MS_PER_DAY = 24 * 60 * 60 * 1e3;
@@ -28952,7 +28952,8 @@ var researchFetchResponseSchema = z27.object({
28952
28952
  contentChars: z27.number(),
28953
28953
  /** The page was longer than one response may carry, so the tail is missing. */
28954
28954
  truncated: z27.boolean(),
28955
- provider: z27.literal("firecrawl")
28955
+ /** `reddit` when the URL was a Reddit thread, which Firecrawl refuses and our Reddit reader serves instead. */
28956
+ provider: z27.enum(["firecrawl", "reddit"])
28956
28957
  });
28957
28958
  var RESEARCH_KEYWORD_METRICS_MAX_KEYWORDS = 700;
28958
28959
  var RESEARCH_KEYWORD_METRICS_MAX_KEYWORD_CHARS = 80;
@@ -28991,6 +28992,81 @@ var researchKeywordMetricsRowSchema = z27.object({
28991
28992
  var researchKeywordMetricsResponseSchema = z27.object({
28992
28993
  data: z27.array(researchKeywordMetricsRowSchema)
28993
28994
  });
28995
+ var RESEARCH_REDDIT_SEARCH_MAX_LIMIT = 50;
28996
+ var RESEARCH_REDDIT_MAX_SUBREDDITS = 10;
28997
+ var RESEARCH_REDDIT_THREAD_MAX_COMMENTS = 200;
28998
+ var RESEARCH_REDDIT_THREAD_DEFAULT_COMMENTS = 40;
28999
+ var researchRedditSourceSchema = z27.enum(["reddit_api", "reddit_web", "archive"]);
29000
+ var researchRedditSubredditSchema = z27.string().trim().transform((name) => name.replace(/^\/?r\//i, "")).pipe(z27.string().regex(/^[A-Za-z0-9_]{2,21}$/, "Not a subreddit name"));
29001
+ var researchRedditSearchRequestSchema = z27.object({
29002
+ query: z27.string().trim().min(1).max(256),
29003
+ subreddits: z27.array(researchRedditSubredditSchema).max(RESEARCH_REDDIT_MAX_SUBREDDITS).default([]),
29004
+ /** `comments` searches what people replied, which is where most of the verbatim voice is. */
29005
+ kind: z27.enum(["posts", "comments"]).default("posts"),
29006
+ /** Only things written on or after this day, `YYYY-MM-DD`. */
29007
+ since: z27.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Use YYYY-MM-DD").optional(),
29008
+ limit: z27.coerce.number().int().min(1).max(RESEARCH_REDDIT_SEARCH_MAX_LIMIT).default(25)
29009
+ });
29010
+ var researchRedditThreadRequestSchema = z27.object({
29011
+ /** A thread link in any form Reddit hands out — full, `old.`, `redd.it`, `/s/` share — or a bare post id. */
29012
+ target: z27.string().trim().min(1).max(2048),
29013
+ comments: z27.coerce.number().int().min(0).max(RESEARCH_REDDIT_THREAD_MAX_COMMENTS).default(RESEARCH_REDDIT_THREAD_DEFAULT_COMMENTS)
29014
+ });
29015
+ var researchRedditItemSchema = z27.object({
29016
+ kind: z27.enum(["post", "comment"]),
29017
+ id: z27.string(),
29018
+ postId: z27.string(),
29019
+ subreddit: z27.string(),
29020
+ /** `null` once the account is deleted. */
29021
+ author: z27.string().nullable(),
29022
+ /** The post's title; on a comment, the title of the post it sits under when known. */
29023
+ title: z27.string().nullable(),
29024
+ /** Verbatim: the post's own text (empty for a link post) or the comment's. */
29025
+ text: z27.string(),
29026
+ score: z27.number().nullable(),
29027
+ /** Posts only. */
29028
+ commentCount: z27.number().nullable(),
29029
+ createdAt: z27.string(),
29030
+ permalink: z27.string(),
29031
+ /** Comments inside a thread: 0 answers the post, 1 answers a comment, and so on. */
29032
+ depth: z27.number().nullable()
29033
+ });
29034
+ var researchRedditAttemptSchema = z27.object({
29035
+ source: researchRedditSourceSchema,
29036
+ outcome: z27.enum(["ok", "empty", "not_found", "not_configured", "unsupported", "blocked", "unavailable"])
29037
+ });
29038
+ var researchRedditSearchResponseSchema = z27.object({
29039
+ results: z27.array(researchRedditItemSchema),
29040
+ source: researchRedditSourceSchema,
29041
+ /** Every source asked, in order — an empty answer from one is not the same as none from all. */
29042
+ attempts: z27.array(researchRedditAttemptSchema)
29043
+ });
29044
+ var researchRedditThreadResponseSchema = z27.object({
29045
+ post: researchRedditItemSchema,
29046
+ /** Highest-scoring first, each followed by its best replies. Removed and deleted comments are left out. */
29047
+ comments: z27.array(researchRedditItemSchema),
29048
+ source: researchRedditSourceSchema,
29049
+ attempts: z27.array(researchRedditAttemptSchema)
29050
+ });
29051
+ var RESEARCH_REDDIT_SUBREDDITS_MAX_LIMIT = 25;
29052
+ var researchRedditSubredditsRequestSchema = z27.object({
29053
+ query: z27.string().trim().min(2).max(100),
29054
+ limit: z27.coerce.number().int().min(1).max(RESEARCH_REDDIT_SUBREDDITS_MAX_LIMIT).default(10)
29055
+ });
29056
+ var researchRedditSubredditSummarySchema = z27.object({
29057
+ name: z27.string(),
29058
+ /** Members; `null` when the source does not say. Size, not activity — a big subreddit can be quiet. */
29059
+ subscribers: z27.number().nullable(),
29060
+ /** `restricted`: anyone reads, only approved members post. `private`: nothing can be read. */
29061
+ access: z27.enum(["public", "restricted", "private", "other"]).nullable(),
29062
+ description: z27.string(),
29063
+ url: z27.string()
29064
+ });
29065
+ var researchRedditSubredditsResponseSchema = z27.object({
29066
+ results: z27.array(researchRedditSubredditSummarySchema),
29067
+ source: researchRedditSourceSchema,
29068
+ attempts: z27.array(researchRedditAttemptSchema)
29069
+ });
28994
29070
 
28995
29071
  // ../api/src/scheduledAgentRules.ts
28996
29072
  import { z as z28 } from "zod";
@@ -30313,7 +30389,11 @@ var videoAnalysisSchema = z35.object({
30313
30389
  shotStyle: z35.string(),
30314
30390
  audioStyle: z35.string(),
30315
30391
  hook: z35.string(),
30316
- usageNotes: z35.string()
30392
+ usageNotes: z35.string(),
30393
+ // Optional because rows analysed before analysis version 2 do not carry them.
30394
+ format: z35.string().optional(),
30395
+ tone: z35.string().optional(),
30396
+ productClaims: z35.array(z35.string()).optional()
30317
30397
  });
30318
30398
  var videoDocSchema = z35.object({
30319
30399
  _id: z35.string(),
@@ -31564,9 +31644,9 @@ function toRow(entry) {
31564
31644
  };
31565
31645
  }
31566
31646
  function renderEntry(entry, full) {
31567
- const row = toRow(entry);
31568
- const suffix = row.status ? ` [${row.status}]` : "";
31569
- const lines = [`- \`${row.id}\` ${row.name}${suffix}`];
31647
+ const row2 = toRow(entry);
31648
+ const suffix = row2.status ? ` [${row2.status}]` : "";
31649
+ const lines = [`- \`${row2.id}\` ${row2.name}${suffix}`];
31570
31650
  const nested = entry.action;
31571
31651
  const doc = nested && typeof nested === "object" ? nested : entry;
31572
31652
  const description = typeof doc.description === "string" ? doc.description : "";
@@ -31576,8 +31656,8 @@ function renderEntry(entry, full) {
31576
31656
  ...text2.trim().split("\n").map((line) => line ? ` ${line}` : "")
31577
31657
  );
31578
31658
  }
31579
- if (row.hint) {
31580
- lines.push(` ${row.hint}`);
31659
+ if (row2.hint) {
31660
+ lines.push(` ${row2.hint}`);
31581
31661
  }
31582
31662
  return lines;
31583
31663
  }
@@ -35288,10 +35368,10 @@ function membersFromFile(content) {
35288
35368
  const emailColumn = header.findIndex((cell3) => EMAIL_HEADER.test(cell3));
35289
35369
  const phoneColumn = header.findIndex((cell3) => !EMAIL_HEADER.test(cell3) && PHONE_HEADER.test(cell3));
35290
35370
  if (emailColumn >= 0 || phoneColumn >= 0) {
35291
- const cells = (column) => column < 0 ? [] : rows.slice(1).flatMap((row) => row[column] ? [row[column]] : []);
35371
+ const cells = (column) => column < 0 ? [] : rows.slice(1).flatMap((row2) => row2[column] ? [row2[column]] : []);
35292
35372
  return { emails: cells(emailColumn), phones: cells(phoneColumn) };
35293
35373
  }
35294
- const values = rows.flatMap((row) => row[0] ? [row[0]] : []);
35374
+ const values = rows.flatMap((row2) => row2[0] ? [row2[0]] : []);
35295
35375
  return values.some((value2) => value2.includes("@")) ? { emails: values, phones: [] } : { emails: [], phones: values };
35296
35376
  }
35297
35377
 
@@ -35820,7 +35900,7 @@ function conversionReportRequestFromArgs(args8) {
35820
35900
  };
35821
35901
  }
35822
35902
  function reportHints(report) {
35823
- const nothing = report.rows.every((row) => row.conversions === 0 && (row.events ?? []).length === 0);
35903
+ const nothing = report.rows.every((row2) => row2.conversions === 0 && (row2.events ?? []).length === 0);
35824
35904
  return nothing ? [
35825
35905
  "No attributed conversions in this window. Before concluding the ads do not convert: `conversions events` shows whether the site's events arrive at all, and `campaigns` whether anything served."
35826
35906
  ] : [
@@ -39331,9 +39411,9 @@ function warnDefaults(ctx) {
39331
39411
  var PLANNER_SOURCE = "keyword_planner_estimate";
39332
39412
  var IDENTITY_FIELDS = /* @__PURE__ */ new Set(["keyword"]);
39333
39413
  function stampPlannerRows(rows) {
39334
- return rows.map((row) => {
39414
+ return rows.map((row2) => {
39335
39415
  const stamped = { source: PLANNER_SOURCE };
39336
- for (const [key, value2] of Object.entries(row)) {
39416
+ for (const [key, value2] of Object.entries(row2)) {
39337
39417
  stamped[IDENTITY_FIELDS.has(key) || key.startsWith("planner_") ? key : `planner_${key}`] = value2;
39338
39418
  }
39339
39419
  return stamped;
@@ -40262,33 +40342,33 @@ function toMicros2(value2) {
40262
40342
  function formatMoney(micros) {
40263
40343
  return (micros / 1e6).toFixed(2);
40264
40344
  }
40265
- function keywordLabel(row) {
40266
- const text2 = row["ad_group_criterion.keyword.text"];
40267
- const id = row["ad_group_criterion.criterion_id"];
40345
+ function keywordLabel(row2) {
40346
+ const text2 = row2["ad_group_criterion.keyword.text"];
40347
+ const id = row2["ad_group_criterion.criterion_id"];
40268
40348
  const name = typeof text2 === "string" && text2 !== "" ? text2 : id == null ? "(unnamed keyword)" : `criterion ${id}`;
40269
- const adGroup = row["ad_group.name"];
40349
+ const adGroup = row2["ad_group.name"];
40270
40350
  return typeof adGroup === "string" && adGroup !== "" ? `${name} (ad group ${adGroup})` : name;
40271
40351
  }
40272
- function keywordKey(row) {
40273
- const adGroup = row["ad_group.id"] ?? row["ad_group.name"] ?? "";
40274
- const criterion = row["ad_group_criterion.criterion_id"] ?? row["ad_group_criterion.keyword.text"] ?? "";
40275
- return `${String(adGroup)}~${String(criterion)}` === "~" ? keywordLabel(row) : `${adGroup}~${criterion}`;
40352
+ function keywordKey(row2) {
40353
+ const adGroup = row2["ad_group.id"] ?? row2["ad_group.name"] ?? "";
40354
+ const criterion = row2["ad_group_criterion.criterion_id"] ?? row2["ad_group_criterion.keyword.text"] ?? "";
40355
+ return `${String(adGroup)}~${String(criterion)}` === "~" ? keywordLabel(row2) : `${adGroup}~${criterion}`;
40276
40356
  }
40277
- function reasonsOn(row) {
40278
- const raw = row["ad_group_criterion.primary_status_reasons"];
40357
+ function reasonsOn(row2) {
40358
+ const raw = row2["ad_group_criterion.primary_status_reasons"];
40279
40359
  if (!Array.isArray(raw)) return null;
40280
40360
  return raw.filter((r) => typeof r === "string");
40281
40361
  }
40282
- function bidShortfall(row) {
40283
- const bid = toMicros2(row["ad_group_criterion.effective_cpc_bid_micros"] ?? row["ad_group_criterion.cpc_bid_micros"]);
40284
- const firstPage = toMicros2(row["ad_group_criterion.position_estimates.first_page_cpc_micros"]);
40362
+ function bidShortfall(row2) {
40363
+ const bid = toMicros2(row2["ad_group_criterion.effective_cpc_bid_micros"] ?? row2["ad_group_criterion.cpc_bid_micros"]);
40364
+ const firstPage = toMicros2(row2["ad_group_criterion.position_estimates.first_page_cpc_micros"]);
40285
40365
  if (bid == null || firstPage == null || bid >= firstPage) return null;
40286
40366
  return `bids ${formatMoney(bid)} against a ${formatMoney(firstPage)} first-page estimate`;
40287
40367
  }
40288
- function limitsOn(row, options) {
40289
- const reasons = reasonsOn(row);
40290
- const shortfall = bidShortfall(row);
40291
- const label = keywordLabel(row);
40368
+ function limitsOn(row2, options) {
40369
+ const reasons = reasonsOn(row2);
40370
+ const shortfall = bidShortfall(row2);
40371
+ const label = keywordLabel(row2);
40292
40372
  const applicable = reasons ? reasons.filter((reason) => reason in ACTIONABLE_REASONS) : options.statusReasonsSelected || shortfall === null ? [] : [BELOW_FIRST_PAGE_BID];
40293
40373
  return applicable.map((reason) => ({
40294
40374
  reason,
@@ -40302,10 +40382,10 @@ function describeLimit(reason, details) {
40302
40382
  return `${label} \u2014 ${details.length}: ${named}${rest}. Fix: ${advice}.`;
40303
40383
  }
40304
40384
  function collectKeywordLimits(rows, options, acc = { detailsByReason: /* @__PURE__ */ new Map(), keywords: /* @__PURE__ */ new Set() }) {
40305
- for (const row of rows) {
40306
- const limits = limitsOn(row, options);
40385
+ for (const row2 of rows) {
40386
+ const limits = limitsOn(row2, options);
40307
40387
  if (limits.length === 0) continue;
40308
- acc.keywords.add(keywordKey(row));
40388
+ acc.keywords.add(keywordKey(row2));
40309
40389
  for (const { reason, detail } of limits) {
40310
40390
  const bucket = acc.detailsByReason.get(reason);
40311
40391
  if (bucket) bucket.push(detail);
@@ -40415,11 +40495,11 @@ function writeRowsToFile(filePath, rows, fields, append) {
40415
40495
  writeFileSync(filePath, `${toCsvRow(fields)}
40416
40496
  `, "utf-8");
40417
40497
  }
40418
- const csvRows = rows.map((row) => toCsvRow(fields.map((f) => String(row[f] ?? ""))));
40498
+ const csvRows = rows.map((row2) => toCsvRow(fields.map((f) => String(row2[f] ?? ""))));
40419
40499
  appendFileSync(filePath, `${csvRows.join("\n")}
40420
40500
  `, "utf-8");
40421
40501
  } else if (filePath.split(".").pop()?.toLowerCase() === "jsonl") {
40422
- const lines = rows.map((row) => JSON.stringify(row));
40502
+ const lines = rows.map((row2) => JSON.stringify(row2));
40423
40503
  if (append && existsSync2(filePath)) {
40424
40504
  appendFileSync(filePath, `${lines.join("\n")}
40425
40505
  `, "utf-8");
@@ -40504,26 +40584,26 @@ function writeLine(line) {
40504
40584
  var writeCsvRows = (rows, fields, notices) => {
40505
40585
  writeLine(toCsvRow(fields));
40506
40586
  for (const line of notices) writeLine(line);
40507
- for (const row of rows) writeLine(toCsvRow(fields.map((f) => String(row[f] ?? ""))));
40587
+ for (const row2 of rows) writeLine(toCsvRow(fields.map((f) => String(row2[f] ?? ""))));
40508
40588
  };
40509
40589
  var writeJsonlRows = (rows, _fields, notices) => {
40510
40590
  for (const line of notices) writeLine(line);
40511
- for (const row of rows) writeLine(JSON.stringify(row));
40591
+ for (const row2 of rows) writeLine(JSON.stringify(row2));
40512
40592
  };
40513
40593
  var OMITTED = "(omitted)";
40514
40594
  var OMITTED_FALSE = `false ${OMITTED}`;
40515
40595
  var OMITTED_LEGEND = `> ${OMITTED} = Google left this field off the row, which is how it sends false, 0 and "not set": the field WAS read. A field with no column here was not read.`;
40516
- function mdCell(row, field, booleanColumns) {
40517
- if (!(field in row)) return booleanColumns.has(field) ? OMITTED_FALSE : OMITTED;
40518
- return String(row[field] ?? "").replace(/\|/g, "\\|");
40596
+ function mdCell(row2, field, booleanColumns) {
40597
+ if (!(field in row2)) return booleanColumns.has(field) ? OMITTED_FALSE : OMITTED;
40598
+ return String(row2[field] ?? "").replace(/\|/g, "\\|");
40519
40599
  }
40520
40600
  var writeMdRows = (rows, fields, notices) => {
40521
- const booleanColumns = new Set(fields.filter((f) => rows.some((row) => typeof row[f] === "boolean")));
40601
+ const booleanColumns = new Set(fields.filter((f) => rows.some((row2) => typeof row2[f] === "boolean")));
40522
40602
  for (const line of notices) writeLine(line);
40523
40603
  writeLine(`| ${fields.join(" | ")} |`);
40524
40604
  writeLine(`| ${fields.map(() => "---").join(" | ")} |`);
40525
- for (const row of rows) writeLine(`| ${fields.map((f) => mdCell(row, f, booleanColumns)).join(" | ")} |`);
40526
- if (rows.some((row) => fields.some((f) => !(f in row)))) writeLine(OMITTED_LEGEND);
40605
+ for (const row2 of rows) writeLine(`| ${fields.map((f) => mdCell(row2, f, booleanColumns)).join(" | ")} |`);
40606
+ if (rows.some((row2) => fields.some((f) => !(f in row2)))) writeLine(OMITTED_LEGEND);
40527
40607
  };
40528
40608
  var ROW_WRITERS = { csv: writeCsvRows, jsonl: writeJsonlRows, md: writeMdRows };
40529
40609
  function writeRows(format, rows, columns, notices) {
@@ -40647,8 +40727,8 @@ function parseSelectedFields(gaql) {
40647
40727
  return list.split(",").map((field) => field.trim()).filter((field) => /^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z0-9_]+)*$/.test(field));
40648
40728
  }
40649
40729
  function foldObservedFields(rows, observed) {
40650
- for (const row of rows) {
40651
- for (const key of Object.keys(row)) observed.add(key);
40730
+ for (const row2 of rows) {
40731
+ for (const key of Object.keys(row2)) observed.add(key);
40652
40732
  }
40653
40733
  }
40654
40734
  function unobservedFields(gaql, observed) {
@@ -41483,11 +41563,11 @@ var PROPERTY_ROW_PREFIX = "properties.";
41483
41563
  var PROPERTY_KEY_MAX = 64;
41484
41564
  var PROPERTY_KEY = /^[A-Za-z0-9_][A-Za-z0-9_.-]*$/;
41485
41565
  var RESERVED_PROPERTY_KEYS = ["baker_source", "hubspot_property", "hubspot_value"];
41486
- function isPropertyRow(row) {
41487
- return row.startsWith(PROPERTY_ROW_PREFIX);
41566
+ function isPropertyRow(row2) {
41567
+ return row2.startsWith(PROPERTY_ROW_PREFIX);
41488
41568
  }
41489
- function propertyKeyOf(row) {
41490
- return isPropertyRow(row) ? row.slice(PROPERTY_ROW_PREFIX.length) : null;
41569
+ function propertyKeyOf(row2) {
41570
+ return isPropertyRow(row2) ? row2.slice(PROPERTY_ROW_PREFIX.length) : null;
41491
41571
  }
41492
41572
  function propertyKeyProblem(key) {
41493
41573
  const trimmed = key.trim();
@@ -41499,11 +41579,11 @@ function propertyKeyProblem(key) {
41499
41579
  if (RESERVED_PROPERTY_KEYS.includes(trimmed)) return `"${trimmed}" is written by Baker itself; pick another name.`;
41500
41580
  return null;
41501
41581
  }
41502
- function mappingRowProblem(row) {
41503
- if (isSourceField(row)) return null;
41504
- const key = propertyKeyOf(row);
41582
+ function mappingRowProblem(row2) {
41583
+ if (isSourceField(row2)) return null;
41584
+ const key = propertyKeyOf(row2);
41505
41585
  if (key === null) {
41506
- return `"${row}" is not a field an event can carry. A field is one of ${SOURCE_FIELD_NAMES.join(", ")}; an extra property is spelled properties.<name>.`;
41586
+ return `"${row2}" is not a field an event can carry. A field is one of ${SOURCE_FIELD_NAMES.join(", ")}; an extra property is spelled properties.<name>.`;
41507
41587
  }
41508
41588
  return propertyKeyProblem(key);
41509
41589
  }
@@ -42090,13 +42170,13 @@ var BOOKKEEPING_TYPES = /* @__PURE__ */ new Set([
42090
42170
  "side_effect_settled"
42091
42171
  ]);
42092
42172
  var SUBMIT_TRIGGERS = new Set(Object.values(STEP_SUBMIT_TRIGGERS));
42093
- function isEmbedLifecycle(row) {
42094
- return row.eventType === "form_trigger" && triggerDescription2(row.triggerId)?.lifecycle === true;
42173
+ function isEmbedLifecycle(row2) {
42174
+ return row2.eventType === "form_trigger" && triggerDescription2(row2.triggerId)?.lifecycle === true;
42095
42175
  }
42096
- function isBookkeepingRow(row) {
42097
- if (BOOKKEEPING_TYPES.has(row.eventType)) return true;
42098
- if (isEmbedLifecycle(row)) return true;
42099
- return row.eventType === "form_trigger" && SUBMIT_TRIGGERS.has(row.triggerId);
42176
+ function isBookkeepingRow(row2) {
42177
+ if (BOOKKEEPING_TYPES.has(row2.eventType)) return true;
42178
+ if (isEmbedLifecycle(row2)) return true;
42179
+ return row2.eventType === "form_trigger" && SUBMIT_TRIGGERS.has(row2.triggerId);
42100
42180
  }
42101
42181
 
42102
42182
  // ../api/src/analytics/identityLinks.ts
@@ -42325,13 +42405,13 @@ var CLICK_LABELS2 = {
42325
42405
  sccid: "Snapchat click id",
42326
42406
  oppref: "ChatGPT Ads click id"
42327
42407
  };
42328
- var PERSON_FIELDS = MATCH_FIELD_FORWARDING.map((row) => ({
42329
- name: row.field,
42330
- label: PERSON_LABELS[row.field] ?? row.field,
42408
+ var PERSON_FIELDS = MATCH_FIELD_FORWARDING.map((row2) => ({
42409
+ name: row2.field,
42410
+ label: PERSON_LABELS[row2.field] ?? row2.field,
42331
42411
  group: "person",
42332
- hashed: row.hashed,
42333
- ...row.meta ? { meta: row.meta } : {},
42334
- ...row.google ? { google: row.google } : {}
42412
+ hashed: row2.hashed,
42413
+ ...row2.meta ? { meta: row2.meta } : {},
42414
+ ...row2.google ? { google: row2.google } : {}
42335
42415
  }));
42336
42416
  var CLICK_FIELDS = INGEST_CLICK_ID_PARAMS.map((param) => ({
42337
42417
  name: param,
@@ -44707,18 +44787,18 @@ var ADDRESS_KEYS = [
44707
44787
  "city",
44708
44788
  "administrativeArea"
44709
44789
  ];
44710
- function nestAddress(row) {
44790
+ function nestAddress(row2) {
44711
44791
  const address = {};
44712
44792
  for (const key of ADDRESS_KEYS) {
44713
- if (row[key] !== void 0) {
44714
- address[key] = row[key];
44715
- delete row[key];
44793
+ if (row2[key] !== void 0) {
44794
+ address[key] = row2[key];
44795
+ delete row2[key];
44716
44796
  }
44717
44797
  }
44718
- return Object.keys(address).length > 0 ? { ...row, address } : row;
44798
+ return Object.keys(address).length > 0 ? { ...row2, address } : row2;
44719
44799
  }
44720
44800
  function toRow2(record) {
44721
- const row = {};
44801
+ const row2 = {};
44722
44802
  const ignored = [];
44723
44803
  for (const [column, raw] of Object.entries(record)) {
44724
44804
  const key = payloadKeyForColumn(column);
@@ -44730,10 +44810,10 @@ function toRow2(record) {
44730
44810
  }
44731
44811
  const value2 = cellValue(key, raw);
44732
44812
  if (value2 !== void 0) {
44733
- row[key] = value2;
44813
+ row2[key] = value2;
44734
44814
  }
44735
44815
  }
44736
- return { row: nestAddress(row), ignored };
44816
+ return { row: nestAddress(row2), ignored };
44737
44817
  }
44738
44818
  function cellValue(key, raw) {
44739
44819
  const value2 = typeof raw === "string" ? raw.trim() : raw;
@@ -44922,42 +45002,42 @@ function rowFromFlags(args8) {
44922
45002
  ["regionCode", args8.country],
44923
45003
  ["postalCode", args8["postal-code"]]
44924
45004
  ];
44925
- const row = {};
45005
+ const row2 = {};
44926
45006
  for (const [key, value2] of entries) {
44927
45007
  if (typeof value2 === "string" && value2.trim() !== "") {
44928
- row[key] = value2.trim();
45008
+ row2[key] = value2.trim();
44929
45009
  }
44930
45010
  }
44931
- nestAddressFlags(row);
44932
- readNumericFlags(args8, row);
44933
- return Object.keys(row).length > 0 ? row : void 0;
45011
+ nestAddressFlags(row2);
45012
+ readNumericFlags(args8, row2);
45013
+ return Object.keys(row2).length > 0 ? row2 : void 0;
44934
45014
  }
44935
- function nestAddressFlags(row) {
45015
+ function nestAddressFlags(row2) {
44936
45016
  const address = {};
44937
45017
  for (const key of ["givenName", "familyName", "regionCode", "postalCode"]) {
44938
- if (row[key] !== void 0) {
44939
- address[key] = row[key];
44940
- delete row[key];
45018
+ if (row2[key] !== void 0) {
45019
+ address[key] = row2[key];
45020
+ delete row2[key];
44941
45021
  }
44942
45022
  }
44943
45023
  if (Object.keys(address).length > 0) {
44944
- row.address = address;
45024
+ row2.address = address;
44945
45025
  }
44946
45026
  }
44947
- function readNumericFlags(args8, row) {
45027
+ function readNumericFlags(args8, row2) {
44948
45028
  if (args8["conversion-count"] !== void 0 && args8["conversion-count"] !== "") {
44949
45029
  const count2 = Number(args8["conversion-count"]);
44950
45030
  if (!Number.isInteger(count2) || count2 < 1) {
44951
45031
  failWriteValidation2("--conversion-count must be a whole number of conversions, e.g. 3");
44952
45032
  }
44953
- row.conversionCount = count2;
45033
+ row2.conversionCount = count2;
44954
45034
  }
44955
45035
  if (args8.value !== void 0 && args8.value !== "") {
44956
45036
  const numeric = Number(args8.value);
44957
45037
  if (!Number.isFinite(numeric)) {
44958
45038
  failWriteValidation2("--value must be a number in major currency units, e.g. 49.90");
44959
45039
  }
44960
- row.value = numeric;
45040
+ row2.value = numeric;
44961
45041
  }
44962
45042
  }
44963
45043
  var conversionsUploadCommand = defineCommand50({
@@ -46956,16 +47036,16 @@ function parseListFileArg(path58, maxRows) {
46956
47036
  const rows = [];
46957
47037
  for (const line of lines.slice(1)) {
46958
47038
  const cells = parseCsvLine(line);
46959
- const row = {};
47039
+ const row2 = {};
46960
47040
  columns.forEach((column, index) => {
46961
47041
  const value2 = cells[index] ?? "";
46962
47042
  if (!value2) {
46963
47043
  return;
46964
47044
  }
46965
- row[column] = column.toLowerCase() === "email" ? sha256Lower(value2) : value2;
47045
+ row2[column] = column.toLowerCase() === "email" ? sha256Lower(value2) : value2;
46966
47046
  });
46967
- if (Object.keys(row).length > 0) {
46968
- rows.push(row);
47047
+ if (Object.keys(row2).length > 0) {
47048
+ rows.push(row2);
46969
47049
  }
46970
47050
  }
46971
47051
  if (rows.length > maxRows) {
@@ -49830,8 +49910,8 @@ import { defineCommand as defineCommand64 } from "citty";
49830
49910
  function asRecord(value2) {
49831
49911
  return typeof value2 === "object" && value2 !== null && !Array.isArray(value2) ? value2 : void 0;
49832
49912
  }
49833
- function readPath2(row, path58) {
49834
- let current = row;
49913
+ function readPath2(row2, path58) {
49914
+ let current = row2;
49835
49915
  for (const segment of path58.split(".")) {
49836
49916
  const record = asRecord(current);
49837
49917
  if (!record) return void 0;
@@ -49840,10 +49920,10 @@ function readPath2(row, path58) {
49840
49920
  return current;
49841
49921
  }
49842
49922
  function projectFields(rows, paths) {
49843
- return rows.map((row) => {
49923
+ return rows.map((row2) => {
49844
49924
  const projected = {};
49845
49925
  for (const path58 of paths) {
49846
- const value2 = readPath2(row, path58);
49926
+ const value2 = readPath2(row2, path58);
49847
49927
  if (value2 !== void 0) {
49848
49928
  projected[path58] = value2;
49849
49929
  }
@@ -49857,19 +49937,19 @@ function shapeListRows(rows, args8, compact5) {
49857
49937
  if (fields) {
49858
49938
  const paths = fields.split(",").map((field) => field.trim()).filter((field) => field.length > 0);
49859
49939
  if (paths.length > 0) {
49860
- const projected = projectFields(raw, paths).map((row, i) => {
49861
- const missing = paths.filter((path58) => row[path58] === void 0);
49940
+ const projected = projectFields(raw, paths).map((row2, i) => {
49941
+ const missing = paths.filter((path58) => row2[path58] === void 0);
49862
49942
  const source = rows[i];
49863
- if (missing.length === 0 || source === void 0) return row;
49943
+ if (missing.length === 0 || source === void 0) return row2;
49864
49944
  const compactRow3 = compact5.row(source);
49865
49945
  for (const path58 of missing) {
49866
- if (compactRow3[path58] !== void 0) row[path58] = compactRow3[path58];
49946
+ if (compactRow3[path58] !== void 0) row2[path58] = compactRow3[path58];
49867
49947
  }
49868
- return row;
49948
+ return row2;
49869
49949
  });
49870
- const unmatched = paths.filter((path58) => projected.every((row) => row[path58] === void 0));
49950
+ const unmatched = paths.filter((path58) => projected.every((row2) => row2[path58] === void 0));
49871
49951
  if (projected.length === 0 || unmatched.length === 0) return { rows: projected, fields: paths };
49872
- const carried = /* @__PURE__ */ new Set([...compact5.columns, ...raw.flatMap((row) => Object.keys(row))]);
49952
+ const carried = /* @__PURE__ */ new Set([...compact5.columns, ...raw.flatMap((row2) => Object.keys(row2))]);
49873
49953
  return {
49874
49954
  rows: projected,
49875
49955
  fields: paths,
@@ -49881,7 +49961,7 @@ function shapeListRows(rows, args8, compact5) {
49881
49961
  }
49882
49962
  if (args8.full) {
49883
49963
  const columns = /* @__PURE__ */ new Set();
49884
- for (const row of raw) for (const key of Object.keys(row)) columns.add(key);
49964
+ for (const row2 of raw) for (const key of Object.keys(row2)) columns.add(key);
49885
49965
  return { rows: raw, fields: [...columns] };
49886
49966
  }
49887
49967
  return { rows: rows.map(compact5.row), fields: [...compact5.columns] };
@@ -49917,28 +49997,28 @@ var COMPACT_COLUMNS = [
49917
49997
  "landingUrl",
49918
49998
  "postUrn"
49919
49999
  ];
49920
- function compactRow(row) {
49921
- const destination = creativeDestination(row.content);
50000
+ function compactRow(row2) {
50001
+ const destination = creativeDestination(row2.content);
49922
50002
  return {
49923
- id: row.id,
49924
- urn: row.urn,
49925
- campaign: row.campaign,
49926
- intendedStatus: row.intendedStatus,
49927
- isServing: row.isServing,
49928
- reviewStatus: row.reviewStatus,
50003
+ id: row2.id,
50004
+ urn: row2.urn,
50005
+ campaign: row2.campaign,
50006
+ intendedStatus: row2.intendedStatus,
50007
+ isServing: row2.isServing,
50008
+ reviewStatus: row2.reviewStatus,
49929
50009
  format: destination.format,
49930
50010
  ...destination.landingUrl ? { landingUrl: destination.landingUrl } : {},
49931
50011
  ...destination.postUrn ? { postUrn: destination.postUrn } : {}
49932
50012
  };
49933
50013
  }
49934
50014
  function unmappedShapeWarnings(rows, rawContentReturned) {
49935
- const unmapped = rows.filter((row) => row.content && creativeDestination(row.content).format === "unknown");
50015
+ const unmapped = rows.filter((row2) => row2.content && creativeDestination(row2.content).format === "unknown");
49936
50016
  if (unmapped.length === 0) return [];
49937
50017
  const remedy = rawContentReturned ? "Their raw content is in this response \u2014 read it directly; this command cannot resolve a destination for them." : "Re-read those with --full before drawing any conclusion about where they point.";
49938
50018
  return [
49939
50019
  {
49940
50020
  code: "UNMAPPED_CREATIVE_CONTENT",
49941
- message: `${unmapped.length} of ${rows.length} creatives have a content shape this command can't read, so their destination is missing (ids: ${unmapped.slice(0, 5).map((row) => row.id).join(", ")}). ${remedy}`
50021
+ message: `${unmapped.length} of ${rows.length} creatives have a content shape this command can't read, so their destination is missing (ids: ${unmapped.slice(0, 5).map((row2) => row2.id).join(", ")}). ${remedy}`
49942
50022
  }
49943
50023
  ];
49944
50024
  }
@@ -50976,18 +51056,18 @@ Examples:
50976
51056
  import { defineCommand as defineCommand76 } from "citty";
50977
51057
 
50978
51058
  // src/commands/ads/meta/list-rows.ts
50979
- function pick(row, keys) {
51059
+ function pick(row2, keys) {
50980
51060
  const picked = {};
50981
51061
  for (const key of keys) {
50982
- if (row[key] !== void 0) picked[key] = row[key];
51062
+ if (row2[key] !== void 0) picked[key] = row2[key];
50983
51063
  }
50984
51064
  return picked;
50985
51065
  }
50986
51066
  var CAMPAIGN_COLUMNS = ["id", "name", "effective_status", "daily_budget", "lifetime_budget", "created_date"];
50987
- function compactCampaign(row) {
50988
- const created = row.created_time;
51067
+ function compactCampaign(row2) {
51068
+ const created = row2.created_time;
50989
51069
  return {
50990
- ...pick(row, ["id", "name", "effective_status", "daily_budget", "lifetime_budget"]),
51070
+ ...pick(row2, ["id", "name", "effective_status", "daily_budget", "lifetime_budget"]),
50991
51071
  ...typeof created === "string" ? { created_date: created.slice(0, 10) } : {}
50992
51072
  };
50993
51073
  }
@@ -51006,12 +51086,12 @@ var ADSET_KEYS = [
51006
51086
  "is_dynamic_creative"
51007
51087
  ];
51008
51088
  var ADSET_COLUMNS = [...ADSET_KEYS, "learning_stage", "end_time", "created_time"];
51009
- function compactAdSet(row) {
51010
- const learning = asRecord(row.learning_stage_info)?.status;
51089
+ function compactAdSet(row2) {
51090
+ const learning = asRecord(row2.learning_stage_info)?.status;
51011
51091
  return {
51012
- ...pick(row, ADSET_KEYS),
51092
+ ...pick(row2, ADSET_KEYS),
51013
51093
  ...typeof learning === "string" ? { learning_stage: learning } : {},
51014
- ...pick(row, ["end_time", "created_time"])
51094
+ ...pick(row2, ["end_time", "created_time"])
51015
51095
  };
51016
51096
  }
51017
51097
  var AD_COLUMNS = [
@@ -51032,14 +51112,14 @@ function adIssues(value2) {
51032
51112
  return typeof text2 === "string" && text2 ? [text2] : [];
51033
51113
  });
51034
51114
  }
51035
- function compactAd(row) {
51036
- const creativeId = asRecord(row.creative)?.id;
51037
- const issues = adIssues(row.issues_info);
51115
+ function compactAd(row2) {
51116
+ const creativeId = asRecord(row2.creative)?.id;
51117
+ const issues = adIssues(row2.issues_info);
51038
51118
  return {
51039
- ...pick(row, ["id", "name", "adset_id", "campaign_id", "effective_status"]),
51119
+ ...pick(row2, ["id", "name", "adset_id", "campaign_id", "effective_status"]),
51040
51120
  ...creativeId !== void 0 ? { creative_id: creativeId } : {},
51041
51121
  ...issues.length > 0 ? { issues } : {},
51042
- ...pick(row, ["created_time"])
51122
+ ...pick(row2, ["created_time"])
51043
51123
  };
51044
51124
  }
51045
51125
  var ACTIVITY_COLUMNS = [
@@ -51069,10 +51149,10 @@ function activityChange(extraData) {
51069
51149
  const show = (value2) => value2 === null ? "(none)" : String(value2);
51070
51150
  return `${show(before)} \u2192 ${show(after)}`;
51071
51151
  }
51072
- function compactActivity(row) {
51073
- const change = activityChange(row.extra_data);
51152
+ function compactActivity(row2) {
51153
+ const change = activityChange(row2.extra_data);
51074
51154
  return {
51075
- ...pick(row, ["event_time", "event_type", "actor_name", "object_type", "object_name", "object_id"]),
51155
+ ...pick(row2, ["event_time", "event_type", "actor_name", "object_type", "object_name", "object_id"]),
51076
51156
  ...change !== void 0 ? { change } : {}
51077
51157
  };
51078
51158
  }
@@ -53502,10 +53582,10 @@ Example: baker ads meta ads retag 120252158038400247 --url-tags "utm_source={{si
53502
53582
  }
53503
53583
  },
53504
53584
  run: async ({ args: args8 }) => {
53505
- const { apiPost: apiPost2 } = await import("./client-AY6TPFQ4.js");
53506
- const { requireChatId: requireChatId2 } = await import("./env-CWEGTJF3.js");
53507
- const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-EPF3HHJ4.js");
53508
- const { handleMetaError: handleMetaError2 } = await import("./shared-53G34I3U.js");
53585
+ const { apiPost: apiPost2 } = await import("./client-22ED7NUR.js");
53586
+ const { requireChatId: requireChatId2 } = await import("./env-E565GJBV.js");
53587
+ const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-F6NKGZ6E.js");
53588
+ const { handleMetaError: handleMetaError2 } = await import("./shared-YSUGIUS4.js");
53509
53589
  try {
53510
53590
  const accountId = bareAccountId2(args8);
53511
53591
  const urlTags = typeof args8["url-tags"] === "string" ? args8["url-tags"] : void 0;
@@ -53656,10 +53736,10 @@ function duplicateCommand2(entity, label) {
53656
53736
  replace: { type: "boolean", description: "Pause the original once the copy publishes" }
53657
53737
  },
53658
53738
  run: async ({ args: args8 }) => {
53659
- const { apiPost: apiPost2 } = await import("./client-AY6TPFQ4.js");
53660
- const { requireChatId: requireChatId2 } = await import("./env-CWEGTJF3.js");
53661
- const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-EPF3HHJ4.js");
53662
- const { handleMetaError: handleMetaError2 } = await import("./shared-53G34I3U.js");
53739
+ const { apiPost: apiPost2 } = await import("./client-22ED7NUR.js");
53740
+ const { requireChatId: requireChatId2 } = await import("./env-E565GJBV.js");
53741
+ const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-F6NKGZ6E.js");
53742
+ const { handleMetaError: handleMetaError2 } = await import("./shared-YSUGIUS4.js");
53663
53743
  try {
53664
53744
  const accountId = bareAccountId2(args8);
53665
53745
  const chatId = requireChatId2();
@@ -54084,16 +54164,16 @@ Examples:
54084
54164
 
54085
54165
  // src/commands/ads/meta/custom-conversions.ts
54086
54166
  import { defineCommand as defineCommand84 } from "citty";
54087
- function compactCustomConversion(row) {
54167
+ function compactCustomConversion(row2) {
54088
54168
  return {
54089
- id: row.id,
54090
- name: row.name,
54091
- custom_event_type: row.custom_event_type,
54092
- counts: row.rule_summary,
54093
- pixel_id: row.pixel?.id,
54094
- last_fired_time: row.last_fired_time,
54095
- ...row.is_archived ? { archived: true } : {},
54096
- ...row.is_unavailable ? { unavailable: true } : {}
54169
+ id: row2.id,
54170
+ name: row2.name,
54171
+ custom_event_type: row2.custom_event_type,
54172
+ counts: row2.rule_summary,
54173
+ pixel_id: row2.pixel?.id,
54174
+ last_fired_time: row2.last_fired_time,
54175
+ ...row2.is_archived ? { archived: true } : {},
54176
+ ...row2.is_unavailable ? { unavailable: true } : {}
54097
54177
  };
54098
54178
  }
54099
54179
  function customConversionHints(rows) {
@@ -54105,10 +54185,10 @@ function customConversionHints(rows) {
54105
54185
  const hints2 = [
54106
54186
  "To optimize an ad set toward one: `baker ads meta adsets create \u2026 --optimization-goal OFFSITE_CONVERSIONS --custom-conversion-id <id>`. Baker fills the pixel and the event category from the conversion \u2014 do not also pass --pixel-id or --custom-event-type."
54107
54187
  ];
54108
- const neverFired = rows.filter((row) => !row.is_archived && !row.last_fired_time);
54188
+ const neverFired = rows.filter((row2) => !row2.is_archived && !row2.last_fired_time);
54109
54189
  if (neverFired.length > 0) {
54110
54190
  hints2.push(
54111
- `${neverFired.length === 1 ? "This conversion has" : `${neverFired.length} of these have`} never fired (${neverFired.map((row) => row.name ?? row.id).join(
54191
+ `${neverFired.length === 1 ? "This conversion has" : `${neverFired.length} of these have`} never fired (${neverFired.map((row2) => row2.name ?? row2.id).join(
54112
54192
  ", "
54113
54193
  )}). Meta cannot optimize toward an event it has never seen, so an ad set pointed at one delivers without learning \u2014 check the rule matches a page the site actually serves before spending on it.`
54114
54194
  );
@@ -54146,7 +54226,7 @@ var listCommand10 = defineCommand84({
54146
54226
  if (args8.limit) params.limit = args8.limit;
54147
54227
  if (args8["no-cache"]) params["skip-cache"] = "true";
54148
54228
  const rows = await apiGet("/api/ads/meta/custom-conversions", params);
54149
- const data = args8.full ? rows.map((row) => ({ ...row })) : rows.map((row) => ({ ...compactCustomConversion(row) }));
54229
+ const data = args8.full ? rows.map((row2) => ({ ...row2 })) : rows.map((row2) => ({ ...compactCustomConversion(row2) }));
54150
54230
  const fmt = csvOrJson(args8);
54151
54231
  if (fmt !== "json") {
54152
54232
  writeAdsOutput(data, fmt);
@@ -54406,16 +54486,16 @@ function sumActionValues(value2) {
54406
54486
  function aggregate(rows) {
54407
54487
  const byPlacement = /* @__PURE__ */ new Map();
54408
54488
  let totalImpressions = 0;
54409
- for (const row of rows) {
54410
- const position = row.platform_position;
54489
+ for (const row2 of rows) {
54490
+ const position = row2.platform_position;
54411
54491
  if (typeof position !== "string" || position.length === 0) continue;
54412
- const platform = typeof row.publisher_platform === "string" ? row.publisher_platform : "";
54413
- const impressions = toNumber(row.impressions);
54492
+ const platform = typeof row2.publisher_platform === "string" ? row2.publisher_platform : "";
54493
+ const impressions = toNumber(row2.impressions);
54414
54494
  totalImpressions += impressions;
54415
54495
  const key = `${platform}/${position}`;
54416
54496
  const current = byPlacement.get(key) ?? { platform, position, impressions: 0, plays: 0, playRate: 0, share: 0 };
54417
54497
  current.impressions += impressions;
54418
- current.plays += sumActionValues(row.video_play_actions);
54498
+ current.plays += sumActionValues(row2.video_play_actions);
54419
54499
  byPlacement.set(key, current);
54420
54500
  }
54421
54501
  return [...byPlacement.values()].map((placement) => ({
@@ -54430,14 +54510,14 @@ function previewCommand(placement) {
54430
54510
  }
54431
54511
  function isSingleAdQuery(rows) {
54432
54512
  const adIds = /* @__PURE__ */ new Set();
54433
- for (const row of rows) {
54434
- if (typeof row.ad_id !== "string" || row.ad_id.length === 0) return false;
54435
- adIds.add(row.ad_id);
54513
+ for (const row2 of rows) {
54514
+ if (typeof row2.ad_id !== "string" || row2.ad_id.length === 0) return false;
54515
+ adIds.add(row2.ad_id);
54436
54516
  }
54437
54517
  return adIds.size === 1;
54438
54518
  }
54439
54519
  function placementDeliveryHints(rows) {
54440
- const carriesVideoMetrics = rows.some((row) => Array.isArray(row.video_play_actions));
54520
+ const carriesVideoMetrics = rows.some((row2) => Array.isArray(row2.video_play_actions));
54441
54521
  if (!carriesVideoMetrics || !isSingleAdQuery(rows)) return [];
54442
54522
  const placements = aggregate(rows);
54443
54523
  const judgeable = placements.filter((p) => p.impressions >= MIN_IMPRESSIONS);
@@ -58335,7 +58415,7 @@ function forwarding() {
58335
58415
  "| Field | Meta | Google | Held as |",
58336
58416
  "| --- | --- | --- | --- |",
58337
58417
  ...MATCH_FIELD_FORWARDING.map(
58338
- (row) => `| \`${row.field}\` | \`${row.meta}\` | ${row.google === null ? "\u2014" : `\`${row.google}\``} | ${row.hashed ? "SHA-256" : "plain text"} |`
58418
+ (row2) => `| \`${row2.field}\` | \`${row2.meta}\` | ${row2.google === null ? "\u2014" : `\`${row2.google}\``} | ${row2.hashed ? "SHA-256" : "plain text"} |`
58339
58419
  )
58340
58420
  ].join("\n");
58341
58421
  }
@@ -58909,7 +58989,7 @@ function parseConversionsArgs(raw, argv) {
58909
58989
  return args8;
58910
58990
  }
58911
58991
  function existingNames(existing) {
58912
- return [...new Set(existing.map((row) => row.name))];
58992
+ return [...new Set(existing.map((row2) => row2.name))];
58913
58993
  }
58914
58994
  function checkNameCollision(name, existing) {
58915
58995
  const collision = findNameCollision(name, existingNames(existing));
@@ -58919,7 +58999,7 @@ function checkNameCollision(name, existing) {
58919
58999
  );
58920
59000
  }
58921
59001
  function under(name, existing) {
58922
- const rows = existing.filter((row) => row.name === name);
59002
+ const rows = existing.filter((row2) => row2.name === name);
58923
59003
  if (rows.length === 0) {
58924
59004
  throw new ConversionArgsError(
58925
59005
  `Nothing is counted under "${name}". This company counts: ${existingNames(existing).join(", ") || "nothing yet"}.`
@@ -58939,7 +59019,7 @@ function setFromEvents(args8, existing) {
58939
59019
  }));
58940
59020
  }
58941
59021
  return args8.eventKeys.map((eventKey) => {
58942
- const current = existing.find((row) => row.eventKey === eventKey);
59022
+ const current = existing.find((row2) => row2.eventKey === eventKey);
58943
59023
  if (!current) {
58944
59024
  throw new ConversionArgsError(
58945
59025
  `"${eventKey}" is not counted, so there is no count mode to change. Add it with --event "${eventKey}" --name "<what a person calls it>" --count-mode ${args8.countMode}.`
@@ -58952,52 +59032,52 @@ function setFromRename(args8, existing) {
58952
59032
  if (args8.renameFrom === void 0 || args8.renameTo === void 0) return [];
58953
59033
  const renameTo = args8.renameTo;
58954
59034
  checkNameCollision(renameTo, existing);
58955
- return under(args8.renameFrom, existing).map((row) => ({
58956
- eventKey: row.eventKey,
59035
+ return under(args8.renameFrom, existing).map((row2) => ({
59036
+ eventKey: row2.eventKey,
58957
59037
  name: renameTo,
58958
- countMode: row.countMode
59038
+ countMode: row2.countMode
58959
59039
  }));
58960
59040
  }
58961
59041
  function conversionsWrite(args8, existing) {
58962
59042
  const set = [...setFromEvents(args8, existing), ...setFromRename(args8, existing)];
58963
59043
  const remove = [
58964
59044
  ...args8.removeKeys,
58965
- ...args8.removeName === void 0 ? [] : under(args8.removeName, existing).map((row) => row.eventKey)
59045
+ ...args8.removeName === void 0 ? [] : under(args8.removeName, existing).map((row2) => row2.eventKey)
58966
59046
  ];
58967
59047
  if (set.length === 0 && remove.length === 0) return null;
58968
59048
  return { ...set.length > 0 ? { set } : {}, ...remove.length > 0 ? { remove } : {} };
58969
59049
  }
58970
59050
 
58971
59051
  // src/commands/analytics/conversionCandidates.ts
58972
- function compact3(row) {
59052
+ function compact3(row2) {
58973
59053
  return {
58974
- eventKey: row.eventKey,
58975
- events: row.events,
58976
- sessions: row.sessions,
58977
- ...row.isCounted ? { countedAs: row.countedAs } : {}
59054
+ eventKey: row2.eventKey,
59055
+ events: row2.events,
59056
+ sessions: row2.sessions,
59057
+ ...row2.isCounted ? { countedAs: row2.countedAs } : {}
58978
59058
  };
58979
59059
  }
58980
59060
  function foldByForm(rows) {
58981
59061
  const byForm = /* @__PURE__ */ new Map();
58982
- for (const row of rows) {
58983
- const form = byForm.get(row.flowSlug) ?? { form: row.flowSlug, keys: 0, countedKeys: 0, events: 0 };
59062
+ for (const row2 of rows) {
59063
+ const form = byForm.get(row2.flowSlug) ?? { form: row2.flowSlug, keys: 0, countedKeys: 0, events: 0 };
58984
59064
  form.keys += 1;
58985
- if (row.isCounted) form.countedKeys += 1;
58986
- form.events += row.events;
58987
- byForm.set(row.flowSlug, form);
59065
+ if (row2.isCounted) form.countedKeys += 1;
59066
+ form.events += row2.events;
59067
+ byForm.set(row2.flowSlug, form);
58988
59068
  }
58989
59069
  return [...byForm.values()];
58990
59070
  }
58991
59071
  function candidatesForFlow(catalogue, flowSlug) {
58992
- return flowSlug === void 0 ? [...catalogue] : catalogue.filter((row) => row.flowSlug === flowSlug);
59072
+ return flowSlug === void 0 ? [...catalogue] : catalogue.filter((row2) => row2.flowSlug === flowSlug);
58993
59073
  }
58994
59074
  var FOLDED_FORMS_HINT = "A Form's candidates are folded into its `flows` row (`keys` fired, `events`; no `counted` means it counts nothing), or a `formCandidates` row when not listed. `--candidates --flow <slug>` lists that Form's keys; `--full` adds every field of every key.";
58995
59075
  function candidatesView(rows, options) {
58996
59076
  if (options.full) return { candidates: rows };
58997
59077
  if (options.flowSlug !== void 0) return { candidates: rows.map(compact3) };
58998
- const forms = foldByForm(rows.filter((row) => row.flowSlug !== ""));
59078
+ const forms = foldByForm(rows.filter((row2) => row2.flowSlug !== ""));
58999
59079
  return {
59000
- candidates: rows.filter((row) => row.flowSlug === "").map(compact3),
59080
+ candidates: rows.filter((row2) => row2.flowSlug === "").map(compact3),
59001
59081
  ...forms.length > 0 ? { formCandidates: forms } : {}
59002
59082
  };
59003
59083
  }
@@ -59043,24 +59123,24 @@ function conversionHints2(definitions, candidates, askedForCandidates) {
59043
59123
  "Nothing is counted as a conversion, so EVERY conversion number in every other report is zero \u2014 whatever visitors actually did. Baker never guesses which event is the point of a page. Run `baker analytics conversions --candidates` to see what these pages produce, then count the ones that are worth money."
59044
59124
  );
59045
59125
  if (!askedForCandidates && candidates.length > 0) {
59046
- const top = candidates.slice(0, NAMED_CANDIDATES).map((row) => row.eventKey).join(", ");
59126
+ const top = candidates.slice(0, NAMED_CANDIDATES).map((row2) => row2.eventKey).join(", ");
59047
59127
  hints2.push(`${candidates.length} distinct events are available to count. Busiest: ${top}.`);
59048
59128
  }
59049
59129
  return hints2;
59050
59130
  }
59051
- const silent = definitions.filter((row) => row.lastSeen === null);
59131
+ const silent = definitions.filter((row2) => row2.lastSeen === null);
59052
59132
  if (silent.length > 0) {
59053
59133
  hints2.push(
59054
- `${silent.length} counted outcome(s) have never fired in this window: ${silent.slice(0, NAMED_CANDIDATES).map((row) => row.name).join(
59134
+ `${silent.length} counted outcome(s) have never fired in this window: ${silent.slice(0, NAMED_CANDIDATES).map((row2) => row2.name).join(
59055
59135
  ", "
59056
59136
  )}. Either the page never reaches them, or the wrong event was named \u2014 check against --candidates before reporting a conversion rate.`
59057
59137
  );
59058
59138
  }
59059
- const uncounted = candidates.filter((row) => !row.isCounted);
59139
+ const uncounted = candidates.filter((row2) => !row2.isCounted);
59060
59140
  const busiest = uncounted[0];
59061
59141
  if (busiest && busiest.sessions > 0) {
59062
- const counted = candidates.filter((row) => row.isCounted);
59063
- const busiestCounted = counted.reduce((max, row) => Math.max(max, row.sessions), 0);
59142
+ const counted = candidates.filter((row2) => row2.isCounted);
59143
+ const busiestCounted = counted.reduce((max, row2) => Math.max(max, row2.sessions), 0);
59064
59144
  if (busiest.sessions > busiestCounted) {
59065
59145
  hints2.push(
59066
59146
  `\`${busiest.eventKey}\` happened in ${busiest.sessions} visits and is not counted \u2014 more than anything that is. Check it is not the real outcome.`
@@ -59070,7 +59150,7 @@ function conversionHints2(definitions, candidates, askedForCandidates) {
59070
59150
  return hints2;
59071
59151
  }
59072
59152
  function namingHints(definitions) {
59073
- const names = [...new Set(definitions.map((row) => row.name))];
59153
+ const names = [...new Set(definitions.map((row2) => row2.name))];
59074
59154
  if (names.length === 0) {
59075
59155
  return [
59076
59156
  `Nothing is named yet, so the first name sets the vocabulary for this company. Start from the standard set \u2014 ${STANDARD_CONVERSION_NAMES.map((entry) => entry.name).join(", ")} \u2014 and stay in the company's own language once one exists.`
@@ -59081,29 +59161,29 @@ function namingHints(definitions) {
59081
59161
  ];
59082
59162
  }
59083
59163
  function stagedHints(definitions) {
59084
- const staged = definitions.filter((row) => row.staged === true);
59164
+ const staged = definitions.filter((row2) => row2.staged === true);
59085
59165
  if (staged.length === 0) return [];
59086
59166
  return [
59087
- `Staged on this chat, not counted yet: ${staged.map((row) => `"${row.name}"`).join(", ")}. The person sees ${staged.length === 1 ? "it" : "them"} under Sources in the chat's changes, on the event's own row, and ${staged.length === 1 ? "it counts" : "they count"} from when they publish the chat; discarding the chat drops ${staged.length === 1 ? "it" : "them"}. Publishing also rescores events Baker ALREADY RECORDED under the key \u2014 never anything that happened in HubSpot or elsewhere before the event existed, so an event this chat just made starts at zero. Tell them that, not that it is already counted.`,
59167
+ `Staged on this chat, not counted yet: ${staged.map((row2) => `"${row2.name}"`).join(", ")}. The person sees ${staged.length === 1 ? "it" : "them"} under Sources in the chat's changes, on the event's own row, and ${staged.length === 1 ? "it counts" : "they count"} from when they publish the chat; discarding the chat drops ${staged.length === 1 ? "it" : "them"}. Publishing also rescores events Baker ALREADY RECORDED under the key \u2014 never anything that happened in HubSpot or elsewhere before the event existed, so an event this chat just made starts at zero. Tell them that, not that it is already counted.`,
59088
59168
  'When you talk to the person, call each event by its name ("Appointment scheduled"), never its key (`page:Appointment scheduled`): the key is how Baker spells it internally, and the `page:` part means nothing to them.'
59089
59169
  ];
59090
59170
  }
59091
59171
 
59092
59172
  // src/commands/analytics/conversionList.ts
59093
59173
  function mergeStoredWithCounts(stored, reported) {
59094
- const counts = new Map(reported.map((row) => [row.name, row]));
59174
+ const counts = new Map(reported.map((row2) => [row2.name, row2]));
59095
59175
  const groups = /* @__PURE__ */ new Map();
59096
- for (const row of stored) {
59097
- const group = groups.get(row.name) ?? [];
59098
- group.push(row);
59099
- groups.set(row.name, group);
59176
+ for (const row2 of stored) {
59177
+ const group = groups.get(row2.name) ?? [];
59178
+ group.push(row2);
59179
+ groups.set(row2.name, group);
59100
59180
  }
59101
59181
  return [...groups.entries()].map(([name, rows]) => {
59102
- const modes = new Set(rows.map((row) => row.countMode));
59182
+ const modes = new Set(rows.map((row2) => row2.countMode));
59103
59183
  const counted = counts.get(name);
59104
59184
  return {
59105
59185
  name,
59106
- eventKeys: rows.map((row) => row.eventKey).sort(),
59186
+ eventKeys: rows.map((row2) => row2.eventKey).sort(),
59107
59187
  // A real state and usually an editing accident, which is why it is said
59108
59188
  // out loud rather than resolved to one of them.
59109
59189
  countMode: modes.size === 1 ? [...modes][0] : "mixed",
@@ -59115,7 +59195,7 @@ function mergeStoredWithCounts(stored, reported) {
59115
59195
  unplaced: counted?.unplaced ?? 0,
59116
59196
  lastSeen: counted?.lastSeen ?? null,
59117
59197
  doors: counted?.doors ?? [],
59118
- ...rows.some((row) => row.staged === true) ? { staged: true } : {}
59198
+ ...rows.some((row2) => row2.staged === true) ? { staged: true } : {}
59119
59199
  };
59120
59200
  }).sort((a, b) => a.name.localeCompare(b.name));
59121
59201
  }
@@ -59152,9 +59232,9 @@ function pruneValue(value2, full) {
59152
59232
  }
59153
59233
  return value2;
59154
59234
  }
59155
- function pruneRow(row, full) {
59235
+ function pruneRow(row2, full) {
59156
59236
  const out = {};
59157
- for (const [key, value2] of Object.entries(row)) {
59237
+ for (const [key, value2] of Object.entries(row2)) {
59158
59238
  if (DASHBOARD_ONLY_FIELDS.has(key)) continue;
59159
59239
  if (key === "sparkline" && Array.isArray(value2)) {
59160
59240
  if (!full) continue;
@@ -59171,7 +59251,7 @@ function pruneForAgent(data, full) {
59171
59251
  }
59172
59252
  function isTabular(rows) {
59173
59253
  return rows.every(
59174
- (row) => typeof row === "object" && row !== null && !Array.isArray(row) && Object.values(row).every(
59254
+ (row2) => typeof row2 === "object" && row2 !== null && !Array.isArray(row2) && Object.values(row2).every(
59175
59255
  (value2) => value2 === null || typeof value2 !== "object" || Array.isArray(value2) && value2.every((e) => typeof e !== "object")
59176
59256
  )
59177
59257
  );
@@ -59225,40 +59305,40 @@ function byBlockOrder(a, b) {
59225
59305
  }
59226
59306
  function columnsOf(rows) {
59227
59307
  const seen = [];
59228
- for (const row of rows) {
59229
- for (const key of Object.keys(row)) {
59308
+ for (const row2 of rows) {
59309
+ for (const key of Object.keys(row2)) {
59230
59310
  if (!seen.includes(key)) seen.push(key);
59231
59311
  }
59232
59312
  }
59233
- return seen.filter((key) => rows.some((row) => !isAbsent(row[key]) && row[key] !== null && row[key] !== void 0)).sort(byIdentityFirst);
59313
+ return seen.filter((key) => rows.some((row2) => !isAbsent(row2[key]) && row2[key] !== null && row2[key] !== void 0)).sort(byIdentityFirst);
59234
59314
  }
59235
59315
  function table2(rows) {
59236
59316
  const cols = columnsOf(rows);
59237
59317
  if (cols.length === 0) return "";
59238
59318
  const head = `| ${cols.join(" | ")} |`;
59239
59319
  const rule = `| ${cols.map(() => "---").join(" | ")} |`;
59240
- const body = rows.map((row) => `| ${cols.map((col) => cell2(row[col], col)).join(" | ")} |`);
59320
+ const body = rows.map((row2) => `| ${cols.map((col) => cell2(row2[col], col)).join(" | ")} |`);
59241
59321
  return [head, rule, ...body].join("\n");
59242
59322
  }
59243
59323
  function isRowList(value2) {
59244
59324
  return Array.isArray(value2) && value2.length > 0 && value2.every((entry) => typeof entry === "object" && entry !== null && !Array.isArray(entry));
59245
59325
  }
59246
- function labelOf(row, index) {
59326
+ function labelOf(row2, index) {
59247
59327
  for (const key of IDENTITY_COLUMNS) {
59248
- const value2 = row[key];
59328
+ const value2 = row2[key];
59249
59329
  if (typeof value2 === "string" && value2 !== "") return value2;
59250
59330
  }
59251
59331
  return `#${index + 1}`;
59252
59332
  }
59253
59333
  function nestedBlock(name, rows) {
59254
59334
  const parents = rows.map(
59255
- (row) => Object.fromEntries(Object.entries(row).filter(([, value2]) => !isRowList(value2) && !isPlainRow(value2)))
59335
+ (row2) => Object.fromEntries(Object.entries(row2).filter(([, value2]) => !isRowList(value2) && !isPlainRow(value2)))
59256
59336
  );
59257
59337
  const parts = [`### ${name}`, table2(parents)];
59258
- rows.forEach((row, index) => {
59259
- for (const [key, value2] of Object.entries(row)) {
59260
- if (isRowList(value2)) parts.push(`#### ${name}[${labelOf(row, index)}].${key}`, table2(value2));
59261
- else if (isPlainRow(value2)) parts.push(`#### ${name}[${labelOf(row, index)}].${key}`, table2([value2]));
59338
+ rows.forEach((row2, index) => {
59339
+ for (const [key, value2] of Object.entries(row2)) {
59340
+ if (isRowList(value2)) parts.push(`#### ${name}[${labelOf(row2, index)}].${key}`, table2(value2));
59341
+ else if (isPlainRow(value2)) parts.push(`#### ${name}[${labelOf(row2, index)}].${key}`, table2([value2]));
59262
59342
  }
59263
59343
  });
59264
59344
  return parts.filter((part) => part !== "").join("\n");
@@ -59303,7 +59383,7 @@ function missingBreakdownHint(data, asked) {
59303
59383
  if (asked.eventName === void 0) return [];
59304
59384
  const breakdown = data.eventBreakdown;
59305
59385
  if (Array.isArray(breakdown) && breakdown.length > 0) return [];
59306
- const known = Array.isArray(data.customEvents) ? data.customEvents.flatMap((row) => isPlainRow(row) && typeof row.eventName === "string" ? [row.eventName] : []) : [];
59386
+ const known = Array.isArray(data.customEvents) ? data.customEvents.flatMap((row2) => isPlainRow(row2) && typeof row2.eventName === "string" ? [row2.eventName] : []) : [];
59307
59387
  return [
59308
59388
  `No rows came back for --event ${asked.eventName}${asked.property ? ` --property ${asked.property}` : ""}. That event or property does not exist on these pages${known.length > 0 ? `; the ones that do are: ${known.slice(0, 12).join(", ")}${known.length > 12 ? ", \u2026" : ""}` : ""}.`
59309
59389
  ];
@@ -59348,13 +59428,13 @@ function eventKeysCell(eventKeys) {
59348
59428
  }
59349
59429
  function trafficByForm(candidates) {
59350
59430
  const byForm = /* @__PURE__ */ new Map();
59351
- for (const row of candidates) {
59352
- const form = byForm.get(row.flowSlug) ?? { "endings seen": 0, "endings counted": 0, events: 0, lastSeen: "" };
59353
- if (row.events > 0) form["endings seen"] += 1;
59354
- if (row.isCounted) form["endings counted"] += 1;
59355
- form.events += row.events;
59356
- if (row.lastSeen && row.lastSeen.slice(0, 10) > form.lastSeen) form.lastSeen = row.lastSeen.slice(0, 10);
59357
- byForm.set(row.flowSlug, form);
59431
+ for (const row2 of candidates) {
59432
+ const form = byForm.get(row2.flowSlug) ?? { "endings seen": 0, "endings counted": 0, events: 0, lastSeen: "" };
59433
+ if (row2.events > 0) form["endings seen"] += 1;
59434
+ if (row2.isCounted) form["endings counted"] += 1;
59435
+ form.events += row2.events;
59436
+ if (row2.lastSeen && row2.lastSeen.slice(0, 10) > form.lastSeen) form.lastSeen = row2.lastSeen.slice(0, 10);
59437
+ byForm.set(row2.flowSlug, form);
59358
59438
  }
59359
59439
  return byForm;
59360
59440
  }
@@ -59363,32 +59443,32 @@ var FORM_KEYS_HINT = "A Form's candidates are one row per Form: `baker analytics
59363
59443
  function renderConversionsMarkdown(envelope) {
59364
59444
  const { definitions, flow, flows, candidates } = envelope.data;
59365
59445
  const data = {
59366
- definitions: definitions.map(({ name, eventKeys, ...row }) => ({
59446
+ definitions: definitions.map(({ name, eventKeys, ...row2 }) => ({
59367
59447
  name,
59368
59448
  eventKeys: eventKeysCell(eventKeys),
59369
- ...row
59449
+ ...row2
59370
59450
  }))
59371
59451
  };
59372
59452
  if (flow) {
59373
59453
  data.flow = { slug: flow.slug, noConversionReason: flow.noConversionReason };
59374
59454
  data.flowEndings = flow.endings;
59375
59455
  }
59376
- const traffic = candidates ? trafficByForm(candidates.filter((row) => row.flowSlug !== "")) : /* @__PURE__ */ new Map();
59456
+ const traffic = candidates ? trafficByForm(candidates.filter((row2) => row2.flowSlug !== "")) : /* @__PURE__ */ new Map();
59377
59457
  if (flows) {
59378
- data.flows = flows.map((row) => ({
59379
- slug: row.slug,
59380
- counted: row.counted.map((entry) => `${entry.name} @ ${entry.step}`).join("; "),
59381
- noConversion: row.noConversion,
59382
- ...candidates ? traffic.get(row.slug) ?? NO_TRAFFIC : {}
59458
+ data.flows = flows.map((row2) => ({
59459
+ slug: row2.slug,
59460
+ counted: row2.counted.map((entry) => `${entry.name} @ ${entry.step}`).join("; "),
59461
+ noConversion: row2.noConversion,
59462
+ ...candidates ? traffic.get(row2.slug) ?? NO_TRAFFIC : {}
59383
59463
  }));
59384
59464
  }
59385
59465
  const hints2 = [...envelope.hints ?? []];
59386
59466
  if (candidates) {
59387
- const listed2 = new Set(flows?.map((row) => row.slug));
59467
+ const listed2 = new Set(flows?.map((row2) => row2.slug));
59388
59468
  const unlisted = [...traffic].filter(([slug]) => !listed2.has(slug));
59389
- if (unlisted.length > 0) data.formCandidates = unlisted.map(([form, row]) => ({ form, ...row }));
59390
- data.candidates = candidates.filter((row) => row.flowSlug === "").map(({ doors, flowSlug: _flowSlug, nodeType: _nodeType, ...row }) => ({
59391
- ...row,
59469
+ if (unlisted.length > 0) data.formCandidates = unlisted.map(([form, row2]) => ({ form, ...row2 }));
59470
+ data.candidates = candidates.filter((row2) => row2.flowSlug === "").map(({ doors, flowSlug: _flowSlug, nodeType: _nodeType, ...row2 }) => ({
59471
+ ...row2,
59392
59472
  "doors (events/sessions)": doorsCell(doors)
59393
59473
  }));
59394
59474
  if (traffic.size > 0) hints2.push(FORM_KEYS_HINT);
@@ -59412,18 +59492,18 @@ function analyticsErrorEnvelope(err) {
59412
59492
  function isStreamRow(value2) {
59413
59493
  return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
59414
59494
  }
59415
- function enrich(row) {
59416
- const out = { ...row };
59417
- const destination = typeof row.sideEffectType === "string" ? row.sideEffectType : "";
59495
+ function enrich(row2) {
59496
+ const out = { ...row2 };
59497
+ const destination = typeof row2.sideEffectType === "string" ? row2.sideEffectType : "";
59418
59498
  if (destination !== "") out.destination = destinationName(destination);
59419
- const keys = rowMatchKeys(row.userData);
59499
+ const keys = rowMatchKeys(row2.userData);
59420
59500
  if (keys.length > 0) out.matchedBy = keys.map((key) => key.field);
59421
59501
  return out;
59422
59502
  }
59423
59503
  function shapeFeed(stream, everything) {
59424
59504
  if (!Array.isArray(stream)) return { rows: [], heldBack: 0 };
59425
59505
  const rows = stream.filter(isStreamRow);
59426
- const kept = everything ? rows : rows.filter((row) => !isBookkeepingRow({ eventType: row.eventType ?? "", triggerId: row.triggerId ?? "" }));
59506
+ const kept = everything ? rows : rows.filter((row2) => !isBookkeepingRow({ eventType: row2.eventType ?? "", triggerId: row2.triggerId ?? "" }));
59427
59507
  return { rows: kept.map(enrich), heldBack: rows.length - kept.length };
59428
59508
  }
59429
59509
  function feedHints(shape) {
@@ -59433,7 +59513,7 @@ function feedHints(shape) {
59433
59513
  `${shape.heldBack} ${shape.heldBack === 1 ? "row is" : "rows are"} not listed: rows recorded to work the reports out \u2014 identifications, step views, session starts, a destination's answer already folded onto its dispatch \u2014 and embedded forms and calendars announcing that they drew. Add --everything to see them.`
59434
59514
  );
59435
59515
  }
59436
- const upload = shape.rows.find((row) => row.sideEffectType !== void 0 && row.eventId !== void 0);
59516
+ const upload = shape.rows.find((row2) => row2.sideEffectType !== void 0 && row2.eventId !== void 0);
59437
59517
  if (upload !== void 0) {
59438
59518
  hints2.push(
59439
59519
  `A delivery row carries the event it was made for in \`eventId\`. \`baker analytics feed --uid ${String(upload.eventId)}\` returns that event and every upload made for it, each with the platform's verdict.`
@@ -59442,7 +59522,7 @@ function feedHints(shape) {
59442
59522
  return hints2;
59443
59523
  }
59444
59524
  function peopleSearchHints(search, rows) {
59445
- const found = new Set(rows.flatMap((row) => Array.isArray(row.personTerms) ? row.personTerms : []));
59525
+ const found = new Set(rows.flatMap((row2) => Array.isArray(row2.personTerms) ? row2.personTerms : []));
59446
59526
  return search.filter((term) => !found.has(term.term)).map(
59447
59527
  (term) => term.problem !== "" ? `${term.input}: ${term.problem}` : `${term.input}: nothing in this window on this page. \`baker analytics lookup --person '${term.input}'\` reads everything Baker ever saw of them.`
59448
59528
  );
@@ -59474,10 +59554,10 @@ function joinFlowConversions(candidates, definitions) {
59474
59554
  return candidates.map((candidate) => ({ ...candidate, countedAs: byKey.get(candidate.eventKey) ?? null }));
59475
59555
  }
59476
59556
  function flowConversionHints(slug, rows, noConversionReason) {
59477
- const counted = rows.filter((row) => row.countedAs !== null);
59557
+ const counted = rows.filter((row2) => row2.countedAs !== null);
59478
59558
  if (counted.length > 0) {
59479
59559
  return [
59480
- `Baker counts ${counted.length} of this Form's endings: ${counted.map((row) => `${row.countedAs} (${row.nodeName})`).join(
59560
+ `Baker counts ${counted.length} of this Form's endings: ${counted.map((row2) => `${row2.countedAs} (${row2.nodeName})`).join(
59481
59561
  ", "
59482
59562
  )}. Every other ending here scores nothing \u2014 count another with \`--event <key> --name <outcome>\`, reusing the SAME name to make two Forms one number.`
59483
59563
  ];
@@ -59499,21 +59579,21 @@ function flowCoverage(forms, definitions) {
59499
59579
  const seen = /* @__PURE__ */ new Set();
59500
59580
  return {
59501
59581
  slug,
59502
- counted: rows.filter((row) => row.countedAs !== null).filter((row) => {
59503
- const key = `${row.countedAs} ${row.nodeName}`;
59582
+ counted: rows.filter((row2) => row2.countedAs !== null).filter((row2) => {
59583
+ const key = `${row2.countedAs} ${row2.nodeName}`;
59504
59584
  if (seen.has(key)) return false;
59505
59585
  seen.add(key);
59506
59586
  return true;
59507
- }).map((row) => ({ name: String(row.countedAs), step: row.nodeName })),
59587
+ }).map((row2) => ({ name: String(row2.countedAs), step: row2.nodeName })),
59508
59588
  noConversion: typeof declared === "string" && declared.trim() !== "" ? declared.trim() : null
59509
59589
  };
59510
59590
  });
59511
59591
  }
59512
59592
  function flowCoverageHints(coverage) {
59513
- const unset = coverage.filter((row) => row.counted.length === 0 && row.noConversion === null);
59593
+ const unset = coverage.filter((row2) => row2.counted.length === 0 && row2.noConversion === null);
59514
59594
  if (unset.length === 0) return [];
59515
59595
  return [
59516
- `${unset.length} of ${coverage.length} Form(s) here count nothing, so every report shows them converting nobody: ${unset.map((row) => row.slug).join(
59596
+ `${unset.length} of ${coverage.length} Form(s) here count nothing, so every report shows them converting nobody: ${unset.map((row2) => row2.slug).join(
59517
59597
  ", "
59518
59598
  )}. Read each one's endings with \`baker analytics conversions --flow <slug>\` and name the outcome, or declare on its first step why it has none.`
59519
59599
  ];
@@ -59542,9 +59622,9 @@ function shapeHints(data) {
59542
59622
  );
59543
59623
  }
59544
59624
  }
59545
- const unread = (data.adParams ?? []).filter((row) => row.role === void 0 && row.sessions > 0);
59625
+ const unread = (data.adParams ?? []).filter((row2) => row2.role === void 0 && row2.sessions > 0);
59546
59626
  if (unread.length > 0) {
59547
- const named = unread.slice(0, 8).map((row) => `${row.name} (${row.sessions} visits)`).join(", ");
59627
+ const named = unread.slice(0, 8).map((row2) => `${row2.name} (${row2.sessions} visits)`).join(", ");
59548
59628
  hints2.push(
59549
59629
  `Nothing reads these parameters yet: ${named}${unread.length > 8 ? ", \u2026" : ""}. Their values are not being stored \u2014 only the fact that the name appeared \u2014 so any breakdown over them is empty rather than wrong. Name each one with \`baker analytics map --platform <platform> --set <name>=<role>\`; mapping also fixes the history for a parameter Baker was already capturing.`
59550
59630
  );
@@ -59552,7 +59632,7 @@ function shapeHints(data) {
59552
59632
  const unplaceable = data.adParamsUnattributed ?? [];
59553
59633
  if (unplaceable.length > 0) {
59554
59634
  hints2.push(
59555
- `Some parameters arrive on visits with no click id and no recognised source, so no platform can be named for them: ${unplaceable.map((row) => row.name).join(", ")}. If you know where they come from, map them with --platform on that platform.`
59635
+ `Some parameters arrive on visits with no click id and no recognised source, so no platform can be named for them: ${unplaceable.map((row2) => row2.name).join(", ")}. If you know where they come from, map them with --platform on that platform.`
59556
59636
  );
59557
59637
  }
59558
59638
  return hints2;
@@ -59563,7 +59643,7 @@ function adHierarchyHints(data, platform) {
59563
59643
  if (traffic === void 0 || traffic.sessions < MIN_SESSIONS_FOR_TAGGING_CLAIM) return [];
59564
59644
  if (traffic.taggedSessions === 0) return [];
59565
59645
  const carried = new Set(
59566
- (data.adParams ?? []).filter((row) => row.role !== void 0 && row.awaitingTraffic !== true).map((row) => row.role)
59646
+ (data.adParams ?? []).filter((row2) => row2.role !== void 0 && row2.awaitingTraffic !== true).map((row2) => row2.role)
59567
59647
  );
59568
59648
  const missing = essentialAdDimensions(platform).filter((entry) => !carried.has(entry.role));
59569
59649
  if (missing.length === 0) return [];
@@ -59631,7 +59711,7 @@ function buildAnalyticsHints(data, platform, origin) {
59631
59711
  }
59632
59712
  function campaignOriginHints(rows) {
59633
59713
  const campaigns = rows ?? [];
59634
- const fromAds = campaigns.filter((row) => row.clickPlatform !== "");
59714
+ const fromAds = campaigns.filter((row2) => row2.clickPlatform !== "");
59635
59715
  if (fromAds.length === 0) return [];
59636
59716
  const own = campaigns.length - fromAds.length;
59637
59717
  return [
@@ -59675,28 +59755,28 @@ function peopleLimitError(args8) {
59675
59755
  if (dropped === 0) return void 0;
59676
59756
  return `${terms.length + dropped} people named; one search takes up to ${PEOPLE_SEARCH_MAX_TERMS}. Split the list and run each part.`;
59677
59757
  }
59678
- function compactRow2(row) {
59679
- const clicks = [...row.googleClick ? ["google"] : [], ...row.metaClick ? ["meta"] : []];
59758
+ function compactRow2(row2) {
59759
+ const clicks = [...row2.googleClick ? ["google"] : [], ...row2.metaClick ? ["meta"] : []];
59680
59760
  const candidates = {
59681
- input: row.input,
59682
- readAs: row.kind,
59683
- found: row.found,
59684
- problem: row.problem,
59685
- contacts: row.contacts,
59686
- events: row.events,
59687
- firstSeen: row.firstSeen,
59688
- lastSeen: row.lastSeen,
59689
- pageViews: row.pageViews,
59690
- landings: row.landings,
59691
- visits: row.visits,
59692
- formSubmits: row.formSubmits,
59693
- firstSource: row.firstSource,
59694
- systemEvents: row.systemEvents,
59695
- pageEvents: row.pageEvents,
59696
- uploads: row.uploads,
59697
- uploadedTo: row.uploadedTo,
59761
+ input: row2.input,
59762
+ readAs: row2.kind,
59763
+ found: row2.found,
59764
+ problem: row2.problem,
59765
+ contacts: row2.contacts,
59766
+ events: row2.events,
59767
+ firstSeen: row2.firstSeen,
59768
+ lastSeen: row2.lastSeen,
59769
+ pageViews: row2.pageViews,
59770
+ landings: row2.landings,
59771
+ visits: row2.visits,
59772
+ formSubmits: row2.formSubmits,
59773
+ firstSource: row2.firstSource,
59774
+ systemEvents: row2.systemEvents,
59775
+ pageEvents: row2.pageEvents,
59776
+ uploads: row2.uploads,
59777
+ uploadedTo: row2.uploadedTo,
59698
59778
  clicks,
59699
- knownBy: row.knownBy
59779
+ knownBy: row2.knownBy
59700
59780
  };
59701
59781
  return Object.fromEntries(
59702
59782
  Object.entries(candidates).filter(
@@ -59711,17 +59791,17 @@ function lookupHints(data) {
59711
59791
  `${data.dropped} lines past the limit of ${PEOPLE_SEARCH_MAX_TERMS} were not looked up. Run the rest separately.`
59712
59792
  );
59713
59793
  }
59714
- const unseen = data.people.filter((row) => !row.found && row.problem === "");
59794
+ const unseen = data.people.filter((row2) => !row2.found && row2.problem === "");
59715
59795
  if (unseen.length > 0) {
59716
59796
  hints2.push(
59717
- `Not seen by Baker: ${unseen.map((row) => row.input).join(", ")}. Baker knows a person by what its pages, Forms and connected systems recorded \u2014 a lead that came in through a form or site Baker does not measure, or a source not connected, is not here.`
59797
+ `Not seen by Baker: ${unseen.map((row2) => row2.input).join(", ")}. Baker knows a person by what its pages, Forms and connected systems recorded \u2014 a lead that came in through a form or site Baker does not measure, or a source not connected, is not here.`
59718
59798
  );
59719
59799
  }
59720
- for (const row of data.people.filter((person) => person.problem !== "")) {
59721
- hints2.push(`${row.input}: ${row.problem}`);
59800
+ for (const row2 of data.people.filter((person) => person.problem !== "")) {
59801
+ hints2.push(`${row2.input}: ${row2.problem}`);
59722
59802
  }
59723
- const found = data.people.filter((row) => row.found);
59724
- const neverVisited = found.filter((row) => row.pageViews === 0);
59803
+ const found = data.people.filter((row2) => row2.found);
59804
+ const neverVisited = found.filter((row2) => row2.pageViews === 0);
59725
59805
  if (neverVisited.length > 0) {
59726
59806
  hints2.push(
59727
59807
  `${neverVisited.length} of ${found.length} found never visited a page Baker publishes: Baker knows them only from what their systems reported, so no landing can be credited with them.`
@@ -59735,13 +59815,13 @@ function lookupHints(data) {
59735
59815
  return hints2;
59736
59816
  }
59737
59817
  function lookupReport(data, full = false) {
59738
- const found = data.people.filter((row) => row.found);
59818
+ const found = data.people.filter((row2) => row2.found);
59739
59819
  return {
59740
59820
  summary: {
59741
59821
  searched: data.people.length,
59742
59822
  found: found.length,
59743
59823
  notFound: data.people.length - found.length,
59744
- visitedABakerPage: found.filter((row) => row.pageViews > 0).length
59824
+ visitedABakerPage: found.filter((row2) => row2.pageViews > 0).length
59745
59825
  },
59746
59826
  people: full ? data.people : data.people.map(compactRow2),
59747
59827
  hints: lookupHints(data)
@@ -60216,9 +60296,9 @@ var SOURCE_NAME_MAX = 80;
60216
60296
  var sourceNameSchema = z49.string().trim().min(1).max(SOURCE_NAME_MAX).refine((name) => validateEventName(name) === null, REJECTION_MESSAGES.invalid_name);
60217
60297
  var sourceIdSchema = z49.string().min(1);
60218
60298
  var sourceMappingSchema = z49.record(z49.string(), z49.string().min(1)).superRefine((mapping, ctx) => {
60219
- for (const row of Object.keys(mapping)) {
60220
- const problem = mappingRowProblem(row);
60221
- if (problem !== null) ctx.addIssue({ code: "custom", message: problem, path: [row] });
60299
+ for (const row2 of Object.keys(mapping)) {
60300
+ const problem = mappingRowProblem(row2);
60301
+ if (problem !== null) ctx.addIssue({ code: "custom", message: problem, path: [row2] });
60222
60302
  }
60223
60303
  });
60224
60304
  var currencyCodeSchema = z49.string().trim().toUpperCase().regex(/^[A-Z]{3}$/, "A currency is a three-letter code, like EUR or USD.");
@@ -77167,43 +77247,43 @@ var experimentFinishResponseSchema = z62.object({
77167
77247
 
77168
77248
  // src/commands/experiment/hints.ts
77169
77249
  var LIVE_HINT = {
77170
- keep_running: (row, now) => `${row.experimentId} cannot yet tell the two pages apart \u2014 do not end it hoping for a result. ${describeWait2(row, now)} To the user, say only this in plain words: "${row.summary}" plus how much more data it needs and roughly when. Never quote confidence, chance-better or intervals to them \u2014 those numbers confuse marketers and decide nothing.`,
77171
- winner: (row) => row.winner === "variant" ? `${row.experimentId} has a winner: the new variant. Run \`baker experiment finish --id ${row.experimentId} --keep variant\` to show it to everyone, then \`baker experiment fold\`.` : `${row.experimentId}: the new version is proven worse and is STILL being shown to half the visitors. Run \`baker experiment finish --id ${row.experimentId} --keep original\` now.`,
77172
- 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. End it with \`--keep original\` and test a bigger change \u2014 a headline, an offer, the shape of the page, not a button colour.`,
77173
- cannot_win: (row) => `${row.experimentId}: the new version cannot win \u2014 even its best case is under the lift this test was looking for, so there is nothing left to find. Run \`baker experiment finish --id ${row.experimentId} --keep original --reason numbers_clear\` and write down what it taught.`,
77174
- stopped_early_harmful: (row) => `${row.experimentId} is doing damage and is STILL being shown to visitors. Run \`baker experiment finish --id ${row.experimentId} --keep original\` now.`,
77250
+ keep_running: (row2, now) => `${row2.experimentId} cannot yet tell the two pages apart \u2014 do not end it hoping for a result. ${describeWait2(row2, now)} To the user, say only this in plain words: "${row2.summary}" plus how much more data it needs and roughly when. Never quote confidence, chance-better or intervals to them \u2014 those numbers confuse marketers and decide nothing.`,
77251
+ winner: (row2) => row2.winner === "variant" ? `${row2.experimentId} has a winner: the new variant. Run \`baker experiment finish --id ${row2.experimentId} --keep variant\` to show it to everyone, then \`baker experiment fold\`.` : `${row2.experimentId}: the new version is proven worse and is STILL being shown to half the visitors. Run \`baker experiment finish --id ${row2.experimentId} --keep original\` now.`,
77252
+ no_difference: (row2) => `${row2.experimentId} measured the two pages as alike \u2014 it collected enough traffic to see the change it was looking for, and there was none. End it with \`--keep original\` and test a bigger change \u2014 a headline, an offer, the shape of the page, not a button colour.`,
77253
+ cannot_win: (row2) => `${row2.experimentId}: the new version cannot win \u2014 even its best case is under the lift this test was looking for, so there is nothing left to find. Run \`baker experiment finish --id ${row2.experimentId} --keep original --reason numbers_clear\` and write down what it taught.`,
77254
+ stopped_early_harmful: (row2) => `${row2.experimentId} is doing damage and is STILL being shown to visitors. Run \`baker experiment finish --id ${row2.experimentId} --keep original\` now.`,
77175
77255
  // Unreachable on a live test — only ending one produces it. The Record is
77176
77256
  // exhaustive by type so a verdict added upstream fails to compile here rather
77177
77257
  // than printing nothing.
77178
- inconclusive: (row) => `${row.experimentId} was ended before it concluded and found nothing.`,
77179
- invalid: (row) => `${row.experimentId} cannot be read: ${row.invalidReason === "crossover" ? "too many visitors saw both versions" : "the traffic did not split evenly"}. End it with \`--keep original\` and start again rather than reporting anything from it.`
77258
+ inconclusive: (row2) => `${row2.experimentId} was ended before it concluded and found nothing.`,
77259
+ invalid: (row2) => `${row2.experimentId} cannot be read: ${row2.invalidReason === "crossover" ? "too many visitors saw both versions" : "the traffic did not split evenly"}. End it with \`--keep original\` and start again rather than reporting anything from it.`
77180
77260
  };
77181
- function describeWait2(row, now) {
77182
- if (row.willNotConclude) {
77261
+ function describeWait2(row2, now) {
77262
+ if (row2.willNotConclude) {
77183
77263
  return "This page's traffic will not settle it at any point, so end it with `--keep original` and test a bigger change.";
77184
77264
  }
77185
- if (row.earliestDecisionAt === null) {
77265
+ if (row2.earliestDecisionAt === null) {
77186
77266
  return "This page has produced no visitors yet, so there is nothing to project a finish date from.";
77187
77267
  }
77188
- if (row.stillNeeded === null) {
77189
- return `It already has the data it needs; the verdict waits only until it has seen a full day of visitors \u2014 ${describeWait(row.earliestDecisionAt, now)}.`;
77268
+ if (row2.stillNeeded === null) {
77269
+ return `It already has the data it needs; the verdict waits only until it has seen a full day of visitors \u2014 ${describeWait(row2.earliestDecisionAt, now)}.`;
77190
77270
  }
77191
- const needed = `It needs ${roughCount(row.stillNeeded.visitors)} more visitors (${roughCount(row.stillNeeded.conversions)} more conversions) for an answer if the gap holds \u2014 `;
77192
- return `${needed}${describeWait(row.earliestDecisionAt, now)} at today's traffic (${new Date(row.earliestDecisionAt).toISOString().slice(0, 10)}). A test is gated on this data, never on days.`;
77271
+ const needed = `It needs ${roughCount(row2.stillNeeded.visitors)} more visitors (${roughCount(row2.stillNeeded.conversions)} more conversions) for an answer if the gap holds \u2014 `;
77272
+ return `${needed}${describeWait(row2.earliestDecisionAt, now)} at today's traffic (${new Date(row2.earliestDecisionAt).toISOString().slice(0, 10)}). A test is gated on this data, never on days.`;
77193
77273
  }
77194
- function pausedNote(row) {
77195
- return row.status === "paused" ? `${row.experimentId} is paused \u2014 everyone sees the original. \`baker experiment resume --id ${row.experimentId}\` restarts the split with the same sides. ` : "";
77274
+ function pausedNote(row2) {
77275
+ return row2.status === "paused" ? `${row2.experimentId} is paused \u2014 everyone sees the original. \`baker experiment resume --id ${row2.experimentId}\` restarts the split with the same sides. ` : "";
77196
77276
  }
77197
77277
  function unlearnedHint(experiments) {
77198
77278
  const unlearned = experiments.filter(
77199
- (row) => row.status === "finished" && row.learning === null && row.verdict !== "inconclusive"
77279
+ (row2) => row2.status === "finished" && row2.learning === null && row2.verdict !== "inconclusive"
77200
77280
  );
77201
77281
  if (unlearned.length === 0) return null;
77202
- const ids2 = unlearned.map((row) => row.experimentId).join(", ");
77282
+ const ids2 = unlearned.map((row2) => row2.experimentId).join(", ");
77203
77283
  return `${unlearned.length === 1 ? "One finished test has" : `${unlearned.length} finished tests have`} no learning written down yet (${ids2}). Say what it taught in one or two sentences \u2014 what the client should believe about this page now \u2014 with \`baker experiment update --id <id> --learning "\u2026"\`. It is what \`plan\` hands the next test.`;
77204
77284
  }
77205
77285
  function buildStatusHints(experiments, now = Date.now()) {
77206
- const hints2 = experiments.filter((row) => row.status !== "finished").map((row) => pausedNote(row) + LIVE_HINT[row.verdict](row, now));
77286
+ const hints2 = experiments.filter((row2) => row2.status !== "finished").map((row2) => pausedNote(row2) + LIVE_HINT[row2.verdict](row2, now));
77207
77287
  const unlearned = unlearnedHint(experiments);
77208
77288
  if (unlearned) hints2.push(unlearned);
77209
77289
  if (experiments.length === 0) {
@@ -77483,52 +77563,52 @@ function fullArm(arm) {
77483
77563
  ...arm.snapshotCapturedAt === void 0 ? {} : { snapshotCapturedAt: isoMoment(arm.snapshotCapturedAt) }
77484
77564
  };
77485
77565
  }
77486
- function statusReadout(row, { full, now }) {
77566
+ function statusReadout(row2, { full, now }) {
77487
77567
  const decisionWhen = {
77488
- earliestDecisionAt: isoDay2(row.earliestDecisionAt),
77489
- ...row.earliestDecisionAt === null ? {} : { earliestDecisionIn: describeWait(row.earliestDecisionAt, now) }
77568
+ earliestDecisionAt: isoDay2(row2.earliestDecisionAt),
77569
+ ...row2.earliestDecisionAt === null ? {} : { earliestDecisionIn: describeWait(row2.earliestDecisionAt, now) }
77490
77570
  };
77491
77571
  const moments = {
77492
- startedAt: isoMoment(row.startedAt),
77493
- pausedAt: isoMoment(row.pausedAt),
77494
- finishedAt: isoMoment(row.finishedAt)
77572
+ startedAt: isoMoment(row2.startedAt),
77573
+ pausedAt: isoMoment(row2.pausedAt),
77574
+ finishedAt: isoMoment(row2.finishedAt)
77495
77575
  };
77496
77576
  if (full) {
77497
77577
  return {
77498
- ...row,
77578
+ ...row2,
77499
77579
  ...moments,
77500
77580
  ...decisionWhen,
77501
- control: fullArm(row.control),
77502
- variant: fullArm(row.variant)
77581
+ control: fullArm(row2.control),
77582
+ variant: fullArm(row2.variant)
77503
77583
  };
77504
77584
  }
77505
77585
  return {
77506
- experimentId: row.experimentId,
77507
- landingSlug: row.landingSlug,
77508
- variantSlug: row.variantSlug,
77509
- status: row.status,
77510
- verdict: row.verdict,
77511
- summary: row.summary,
77512
- ...row.winner === null ? {} : { winner: row.winner },
77513
- ...row.invalidReason === null ? {} : { invalidReason: row.invalidReason },
77514
- stillNeeded: row.stillNeeded,
77586
+ experimentId: row2.experimentId,
77587
+ landingSlug: row2.landingSlug,
77588
+ variantSlug: row2.variantSlug,
77589
+ status: row2.status,
77590
+ verdict: row2.verdict,
77591
+ summary: row2.summary,
77592
+ ...row2.winner === null ? {} : { winner: row2.winner },
77593
+ ...row2.invalidReason === null ? {} : { invalidReason: row2.invalidReason },
77594
+ stillNeeded: row2.stillNeeded,
77515
77595
  ...decisionWhen,
77516
- willNotConclude: row.willNotConclude,
77517
- control: compactArm(row.control),
77518
- variant: compactArm(row.variant),
77519
- secondary: row.secondary.map(compactMetric),
77520
- hypothesisStatement: row.hypothesisStatement,
77521
- minDetectableRelativeLift: row.minDetectableRelativeLift,
77522
- goal: row.goal,
77523
- ...row.variantGoal === null ? {} : { variantGoal: row.variantGoal },
77524
- goalLabel: row.goalLabel,
77596
+ willNotConclude: row2.willNotConclude,
77597
+ control: compactArm(row2.control),
77598
+ variant: compactArm(row2.variant),
77599
+ secondary: row2.secondary.map(compactMetric),
77600
+ hypothesisStatement: row2.hypothesisStatement,
77601
+ minDetectableRelativeLift: row2.minDetectableRelativeLift,
77602
+ goal: row2.goal,
77603
+ ...row2.variantGoal === null ? {} : { variantGoal: row2.variantGoal },
77604
+ goalLabel: row2.goalLabel,
77525
77605
  ...moments,
77526
- ...row.status === "finished" ? {
77527
- survivor: row.survivor,
77528
- hypothesisOutcome: row.hypothesisOutcome,
77529
- fold: row.fold,
77530
- learning: row.learning,
77531
- endReason: row.endReason
77606
+ ...row2.status === "finished" ? {
77607
+ survivor: row2.survivor,
77608
+ hypothesisOutcome: row2.hypothesisOutcome,
77609
+ fold: row2.fold,
77610
+ learning: row2.learning,
77611
+ endReason: row2.endReason
77532
77612
  } : {}
77533
77613
  };
77534
77614
  }
@@ -77806,7 +77886,7 @@ var statusCommand7 = defineCommand149({
77806
77886
  writeJsonEnvelope({
77807
77887
  ok: true,
77808
77888
  data: {
77809
- experiments: response.data.experiments.map((row) => statusReadout(row, { full: args8.full === true, now }))
77889
+ experiments: response.data.experiments.map((row2) => statusReadout(row2, { full: args8.full === true, now }))
77810
77890
  },
77811
77891
  hints: buildStatusHints(response.data.experiments, now)
77812
77892
  });
@@ -79202,7 +79282,7 @@ function assertDestinationReady(slug, tree, chosen, into) {
79202
79282
  function collectConfigStatusFor(tree, _slug, sideEffectId) {
79203
79283
  const rows = [];
79204
79284
  collectConfigStatus(tree, rows);
79205
- return rows.find((row) => row.sideEffectId === sideEffectId);
79285
+ return rows.find((row2) => row2.sideEffectId === sideEffectId);
79206
79286
  }
79207
79287
  function slotHintFor(type, into) {
79208
79288
  const others = otherSlots(type, shapeFor(type, into));
@@ -79629,9 +79709,9 @@ function statusFor(slug) {
79629
79709
  return config;
79630
79710
  }
79631
79711
  function pendingInputHints(slug, config) {
79632
- const pending = config.filter((row) => row.needsInput);
79712
+ const pending = config.filter((row2) => row2.needsInput);
79633
79713
  if (pending.length === 0) return [];
79634
- const calls = pending.slice(0, MAX_PENDING_HINTS).map((row) => `${row.kind}: ${requestFlowInputCall(slug, row)}`);
79714
+ const calls = pending.slice(0, MAX_PENDING_HINTS).map((row2) => `${row2.kind}: ${requestFlowInputCall(slug, row2)}`);
79635
79715
  const rest = pending.length - calls.length;
79636
79716
  return [
79637
79717
  `${pending.length} item${pending.length === 1 ? "" : "s"} still need the user. Each is one blocking call, and its result carries the destination \u2014 read the names from there rather than looking them up again.`,
@@ -80555,7 +80635,7 @@ var funnelCommand2 = defineCommand155({
80555
80635
  ...namedByCaller.length === 0 ? {} : { namedByCaller }
80556
80636
  };
80557
80637
  const response = await apiPost("/api/funnel", request);
80558
- const counted = new Map((response.data.counted ?? []).map((row) => [row.eventKey, row.name]));
80638
+ const counted = new Map((response.data.counted ?? []).map((row2) => [row2.eventKey, row2.name]));
80559
80639
  const data = response.data.moments === void 0 ? response.data : { ...response.data, moments: withFormMoments(response.data.moments, forms, counted) };
80560
80640
  const hints2 = [...response.hints ?? [], ...unplacedFormHints(response.data.steps ?? [], forms, counted)];
80561
80641
  writeJsonEnvelope({
@@ -81173,11 +81253,11 @@ function writeRowsToFile2(filePath, rows, append) {
81173
81253
  writeFileSync4(filePath, `${toCsvRow(fields)}
81174
81254
  `, "utf-8");
81175
81255
  }
81176
- const csvRows = rows.map((row) => toCsvRow(fields.map((f) => String(row[f] ?? ""))));
81256
+ const csvRows = rows.map((row2) => toCsvRow(fields.map((f) => String(row2[f] ?? ""))));
81177
81257
  appendFileSync2(filePath, `${csvRows.join("\n")}
81178
81258
  `, "utf-8");
81179
81259
  } else if (ext === "jsonl") {
81180
- const lines = rows.map((row) => JSON.stringify(row));
81260
+ const lines = rows.map((row2) => JSON.stringify(row2));
81181
81261
  const content = `${lines.join("\n")}
81182
81262
  `;
81183
81263
  if (append && existsSync10(filePath)) {
@@ -82174,11 +82254,11 @@ function writeRowsToFile3(filePath, rows, append) {
82174
82254
  writeFileSync5(filePath, `${toCsvRow(fields)}
82175
82255
  `, "utf-8");
82176
82256
  }
82177
- const csvRows = rows.map((row) => toCsvRow(fields.map((f) => String(row[f] ?? ""))));
82257
+ const csvRows = rows.map((row2) => toCsvRow(fields.map((f) => String(row2[f] ?? ""))));
82178
82258
  appendFileSync3(filePath, `${csvRows.join("\n")}
82179
82259
  `, "utf-8");
82180
82260
  } else if (ext === "jsonl") {
82181
- const content = `${rows.map((row) => JSON.stringify(row)).join("\n")}
82261
+ const content = `${rows.map((row2) => JSON.stringify(row2)).join("\n")}
82182
82262
  `;
82183
82263
  if (append && existsSync11(filePath)) {
82184
82264
  appendFileSync3(filePath, content, "utf-8");
@@ -90311,7 +90391,7 @@ async function composeFilmstrip(frames) {
90311
90391
  const width = FRAME_WIDTH * Math.min(GRID_COLUMNS, scaled.length);
90312
90392
  const composites = scaled.flatMap((frame, index) => {
90313
90393
  const column = index % GRID_COLUMNS;
90314
- const row = Math.floor(index / GRID_COLUMNS);
90394
+ const row2 = Math.floor(index / GRID_COLUMNS);
90315
90395
  const label = Buffer.from(
90316
90396
  `<svg width="${FRAME_WIDTH}" height="${LABEL_HEIGHT}">
90317
90397
  <rect width="100%" height="100%" fill="#111827"/>
@@ -90319,8 +90399,8 @@ async function composeFilmstrip(frames) {
90319
90399
  </svg>`
90320
90400
  );
90321
90401
  return [
90322
- { input: label, left: column * FRAME_WIDTH, top: row * cellHeight },
90323
- { input: frame, left: column * FRAME_WIDTH, top: row * cellHeight + LABEL_HEIGHT }
90402
+ { input: label, left: column * FRAME_WIDTH, top: row2 * cellHeight },
90403
+ { input: frame, left: column * FRAME_WIDTH, top: row2 * cellHeight + LABEL_HEIGHT }
90324
90404
  ];
90325
90405
  });
90326
90406
  return await sharp6({
@@ -92477,7 +92557,7 @@ Full guide: __tooling__/docs/tools/baker/media-sources.md`
92477
92557
  });
92478
92558
 
92479
92559
  // src/commands/research/index.ts
92480
- import { defineCommand as defineCommand227 } from "citty";
92560
+ import { defineCommand as defineCommand228 } from "citty";
92481
92561
 
92482
92562
  // src/commands/research/advertisers.ts
92483
92563
  import { defineCommand as defineCommand215 } from "citty";
@@ -92559,7 +92639,7 @@ function keywordMetricsHints({
92559
92639
  }
92560
92640
  const provenance = "DataForSEO resells Google's Keyword Planner volume \u2014 planner_search_volume here is the SAME number `baker ads google keywords metrics` returns, not an independent reading of it. Quoting the two as agreeing sources is not corroboration." + (clickstream ? " clickstream_search_volume IS independent (DataForSEO's own panels); when the two disagree, say which one you are quoting." : " For a genuinely independent estimate of demand, re-run with --clickstream (costs 2x), or use first-party data: `baker gsc query` for what already reaches the site, or the account's own search terms.");
92561
92641
  const hints2 = [provenance];
92562
- const empty = rows.filter((row) => row.search_volume === null).map((row) => row.keyword);
92642
+ const empty = rows.filter((row2) => row2.search_volume === null).map((row2) => row2.keyword);
92563
92643
  if (empty.length > 0) {
92564
92644
  hints2.push(
92565
92645
  `${empty.length} of ${asked.length} keywords came back with no data in ${queryContext.location} / ${queryContext.language}: ${empty.join(", ")}. That is missing coverage, NOT zero demand \u2014 do not report them as unsearched, and do not re-query them reworded. \`baker research autocomplete\` on the head term is the way to find the phrasing that market uses.`
@@ -92567,6 +92647,70 @@ function keywordMetricsHints({
92567
92647
  }
92568
92648
  return hints2;
92569
92649
  }
92650
+ var ARCHIVE_NOTE = "Read from the Reddit archive: the words are as posted, but points and comment counts on anything under ~36h old are as first captured \u2014 don't quote those numbers as current.";
92651
+ function redditSearchHints({
92652
+ resultCount,
92653
+ kind,
92654
+ source
92655
+ }) {
92656
+ const hints2 = [];
92657
+ if (resultCount === 0) {
92658
+ hints2.push(
92659
+ 'Nothing matched. Reddit search matches words, not meaning: try the audience\'s own phrasing (one distinctive word, or a seed from `baker research autocomplete`), a longer --since window, or other subreddits \u2014 `baker research reddit subreddits "<topic>"` finds where the audience actually talks' + (kind === "posts" ? ", and try --kind comments \u2014 most of the voice is in the replies." : ".")
92660
+ );
92661
+ } else if (kind === "posts") {
92662
+ hints2.push(
92663
+ "Titles rarely carry the voice. Read the promising threads with `baker research reddit thread <permalink>` for the replies, or search the replies directly with --kind comments."
92664
+ );
92665
+ }
92666
+ if (source === "archive" && resultCount > 0) hints2.push(ARCHIVE_NOTE);
92667
+ return hints2;
92668
+ }
92669
+ function redditThreadHints({
92670
+ commentsShown,
92671
+ commentsAsked,
92672
+ commentCount,
92673
+ source
92674
+ }) {
92675
+ const hints2 = [];
92676
+ if (commentsAsked > 0 && commentsShown === 0 && (commentCount ?? 0) > 0) {
92677
+ hints2.push(
92678
+ `This thread has ${commentCount} comments but none could be read just now. Quote the post only, and don't describe the replies.`
92679
+ );
92680
+ }
92681
+ if (source === "archive") hints2.push(ARCHIVE_NOTE);
92682
+ return hints2;
92683
+ }
92684
+ var REDDIT_HOST = /(^|\.)reddit\.com$|^redd\.it$/i;
92685
+ function redditSubredditsHints({ resultCount }) {
92686
+ if (resultCount === 0) {
92687
+ return [
92688
+ 'No subreddits matched. Try the audience\'s role or the platform they work on ("amazon flex", "deliveroo", "nurses"), one word at a time, or a web search for `site:reddit.com <topic>`.'
92689
+ ];
92690
+ }
92691
+ return [
92692
+ 'Members say how big a subreddit is, not how active it is. Before mining one, run `baker research reddit search "<word>" --subreddit <name> --since <a recent date>`: nothing back means it is quiet, so pick another. `restricted` can still be read; `private` cannot.'
92693
+ ];
92694
+ }
92695
+ var REDDIT_LINK_REJECTED = {
92696
+ action: "reject",
92697
+ explanation: "Do not quote anything attributed to this link \u2014 it does not lead to those words. Find the real thread with `baker research reddit search`, and if you already wrote a quote from it, remove it."
92698
+ };
92699
+ function redditPageFix(url, code) {
92700
+ let host;
92701
+ try {
92702
+ host = new URL(url).hostname;
92703
+ } catch {
92704
+ return null;
92705
+ }
92706
+ if (!REDDIT_HOST.test(host)) return null;
92707
+ if (code === "NOT_FOUND") return REDDIT_LINK_REJECTED;
92708
+ if (code !== "FORBIDDEN") return null;
92709
+ return {
92710
+ action: "retry_with_changes",
92711
+ explanation: 'Read Reddit with its own commands: `baker research reddit search "<words>" --subreddit <name>` to find what people said, `baker research reddit thread <url>` for one thread.'
92712
+ };
92713
+ }
92570
92714
 
92571
92715
  // src/commands/research/output.ts
92572
92716
  var RESEARCH_DATA_NOTE = "Estimates based on third-party SERP data \u2014 not exact figures. Use for directional insights, not precise measurement.";
@@ -92582,9 +92726,9 @@ function toCsvRow2(values) {
92582
92726
  return v;
92583
92727
  }).join(",");
92584
92728
  }
92585
- function flattenRow(row) {
92729
+ function flattenRow(row2) {
92586
92730
  const flat = {};
92587
- for (const [key, val] of Object.entries(row)) {
92731
+ for (const [key, val] of Object.entries(row2)) {
92588
92732
  flat[key] = typeof val === "object" && val !== null ? JSON.stringify(val) : String(val ?? "");
92589
92733
  }
92590
92734
  return flat;
@@ -92594,15 +92738,15 @@ function writeCsv(data, fields) {
92594
92738
  const cols = fields ?? Object.keys(data[0] ?? {});
92595
92739
  process.stdout.write(`${toCsvRow2(cols)}
92596
92740
  `);
92597
- for (const row of data) {
92598
- const flat = flattenRow(row);
92741
+ for (const row2 of data) {
92742
+ const flat = flattenRow(row2);
92599
92743
  process.stdout.write(`${toCsvRow2(cols.map((f) => flat[f] ?? ""))}
92600
92744
  `);
92601
92745
  }
92602
92746
  }
92603
92747
  function writeJsonl(data) {
92604
- for (const row of data) {
92605
- process.stdout.write(`${JSON.stringify(row)}
92748
+ for (const row2 of data) {
92749
+ process.stdout.write(`${JSON.stringify(row2)}
92606
92750
  `);
92607
92751
  }
92608
92752
  }
@@ -92613,8 +92757,8 @@ function writeMd(data, fields) {
92613
92757
  `);
92614
92758
  process.stdout.write(`| ${cols.map(() => "---").join(" | ")} |
92615
92759
  `);
92616
- for (const row of data) {
92617
- const flat = flattenRow(row);
92760
+ for (const row2 of data) {
92761
+ const flat = flattenRow(row2);
92618
92762
  process.stdout.write(`| ${cols.map((f) => flat[f] ?? "").join(" | ")} |
92619
92763
  `);
92620
92764
  }
@@ -93028,7 +93172,7 @@ Examples:
93028
93172
  })
93029
93173
  });
93030
93174
  } catch (err) {
93031
- handleResearchError(err, fetchFix);
93175
+ handleResearchError(err, (code) => redditPageFix(url, code) ?? fetchFix(code));
93032
93176
  }
93033
93177
  }
93034
93178
  });
@@ -93213,24 +93357,24 @@ import { defineCommand as defineCommand221 } from "citty";
93213
93357
 
93214
93358
  // src/commands/research/keyword-metrics-rows.ts
93215
93359
  var KEYWORD_METRICS_SOURCE = "keyword_planner_estimate_via_dataforseo";
93216
- function toOutputRow(row, options) {
93360
+ function toOutputRow(row2, options) {
93217
93361
  const out = {
93218
93362
  source: KEYWORD_METRICS_SOURCE,
93219
- keyword: row.keyword,
93220
- planner_search_volume: row.search_volume,
93221
- planner_cpc: row.cpc,
93222
- planner_low_top_of_page_bid: row.low_top_of_page_bid,
93223
- planner_high_top_of_page_bid: row.high_top_of_page_bid,
93224
- planner_competition: row.competition,
93225
- planner_competition_index: row.competition_index
93363
+ keyword: row2.keyword,
93364
+ planner_search_volume: row2.search_volume,
93365
+ planner_cpc: row2.cpc,
93366
+ planner_low_top_of_page_bid: row2.low_top_of_page_bid,
93367
+ planner_high_top_of_page_bid: row2.high_top_of_page_bid,
93368
+ planner_competition: row2.competition,
93369
+ planner_competition_index: row2.competition_index
93226
93370
  };
93227
93371
  if (options.clickstream) {
93228
- out.clickstream_search_volume = row.clickstream_search_volume;
93372
+ out.clickstream_search_volume = row2.clickstream_search_volume;
93229
93373
  }
93230
93374
  if (options.full) {
93231
- out.keyword_difficulty = row.keyword_difficulty;
93232
- out.main_intent = row.main_intent;
93233
- out.planner_monthly_searches = row.monthly_searches;
93375
+ out.keyword_difficulty = row2.keyword_difficulty;
93376
+ out.main_intent = row2.main_intent;
93377
+ out.planner_monthly_searches = row2.monthly_searches;
93234
93378
  }
93235
93379
  return out;
93236
93380
  }
@@ -93347,7 +93491,7 @@ Examples:
93347
93491
  skipCache: args8["no-cache"] ? true : void 0
93348
93492
  });
93349
93493
  const columns = { clickstream, full };
93350
- const rows = result.data.map((row) => toOutputRow(row, columns));
93494
+ const rows = result.data.map((row2) => toOutputRow(row2, columns));
93351
93495
  const hints2 = keywordMetricsHints({ rows: result.data, asked: keywords, clickstream, queryContext });
93352
93496
  warnDefaults(queryContext);
93353
93497
  const format = args8.output || "json";
@@ -93563,8 +93707,273 @@ Examples:
93563
93707
  }
93564
93708
  });
93565
93709
 
93566
- // src/commands/research/relevant-pages.ts
93710
+ // src/commands/research/reddit.ts
93567
93711
  import { defineCommand as defineCommand225 } from "citty";
93712
+ var TIMEOUT_MS2 = 18e4;
93713
+ var TEXT_PREVIEW_CHARS = 1500;
93714
+ var ITEM_FIELDS = {
93715
+ kind: "post or comment",
93716
+ subreddit: "Where it was said",
93717
+ title: "The post's title (on a comment: the post it answers)",
93718
+ text: "Verbatim \u2014 quote this, exactly",
93719
+ text_truncated: "The text was cut; re-run with --full before quoting the end of it",
93720
+ author: "Reddit username, or null once the account is deleted",
93721
+ score: "Points",
93722
+ comment_count: "Posts only",
93723
+ created: "Date written (UTC)",
93724
+ permalink: "Cite THIS link \u2014 it is built from where the words actually are",
93725
+ depth: "Thread comments: 0 answers the post, 1 answers a comment",
93726
+ source: "reddit_api / reddit_web = live Reddit; archive = the Arctic Shift archive of Reddit"
93727
+ };
93728
+ registerSchema({
93729
+ command: "research.reddit.search",
93730
+ description: "Search Reddit posts or comments for the audience's own words. Returns verbatim text with a real permalink for each hit. Start here for voice-of-customer work on Reddit.",
93731
+ args: {
93732
+ query: {
93733
+ type: "string",
93734
+ description: "Words to match \u2014 the audience's phrasing, not a description of it",
93735
+ required: true
93736
+ },
93737
+ subreddit: {
93738
+ type: "string",
93739
+ description: "Subreddits to search (up to 10): comma-separated (couriersofreddit,AmazonFlexUK) or the flag repeated. Needed for --kind comments",
93740
+ required: false
93741
+ },
93742
+ kind: {
93743
+ type: "string",
93744
+ description: "posts (default) or comments \u2014 the replies are where most of the voice is",
93745
+ required: false,
93746
+ enum: ["posts", "comments"]
93747
+ },
93748
+ since: { type: "string", description: "Only things written on or after this day, YYYY-MM-DD", required: false },
93749
+ limit: { type: "number", description: "Results to return (1-50, default 25)", required: false },
93750
+ full: {
93751
+ type: "boolean",
93752
+ description: "Return every text whole instead of a preview",
93753
+ required: false,
93754
+ default: false
93755
+ }
93756
+ }
93757
+ });
93758
+ registerSchema({
93759
+ command: "research.reddit.thread",
93760
+ description: "Read one Reddit thread verbatim: the post and its best comments, each with its own permalink. Use it on any Reddit link before quoting it \u2014 it also exposes links that point somewhere other than they claim.",
93761
+ args: {
93762
+ target: {
93763
+ type: "string",
93764
+ description: "The thread's link (any Reddit form, including share links) or post id",
93765
+ required: true
93766
+ },
93767
+ comments: { type: "number", description: "Comments to return, best first (0-200, default 40)", required: false },
93768
+ full: {
93769
+ type: "boolean",
93770
+ description: "Return every text whole instead of a preview",
93771
+ required: false,
93772
+ default: false
93773
+ }
93774
+ }
93775
+ });
93776
+ registerSchema({
93777
+ command: "research.reddit.subreddits",
93778
+ description: "Find the subreddits where an audience talks about a topic, with member counts and whether they can be read. Run it before searching, instead of guessing subreddit names.",
93779
+ args: {
93780
+ query: { type: "string", description: 'The audience or topic, e.g. "amazon flex uk", "deliveroo"', required: true },
93781
+ limit: { type: "number", description: "Subreddits to return (1-25, default 10)", required: false }
93782
+ }
93783
+ });
93784
+ function row(item, full) {
93785
+ const cut = !full && item.text.length > TEXT_PREVIEW_CHARS;
93786
+ return {
93787
+ kind: item.kind,
93788
+ subreddit: item.subreddit,
93789
+ title: item.title,
93790
+ text: cut ? `${item.text.slice(0, TEXT_PREVIEW_CHARS)}\u2026` : item.text,
93791
+ ...cut ? { text_truncated: true } : {},
93792
+ author: item.author,
93793
+ score: item.score,
93794
+ ...item.kind === "post" ? { comment_count: item.commentCount } : {},
93795
+ created: item.createdAt.slice(0, 10),
93796
+ permalink: item.permalink,
93797
+ ...item.depth !== null ? { depth: item.depth } : {}
93798
+ };
93799
+ }
93800
+ function redditFix(code) {
93801
+ if (code === "NOT_FOUND") return REDDIT_LINK_REJECTED;
93802
+ if (code === "BAD_REQUEST" || code === "VALIDATION_ERROR") {
93803
+ return { action: "retry_with_changes", explanation: "Follow the message above and run the command again." };
93804
+ }
93805
+ return {
93806
+ action: "continue_without",
93807
+ explanation: "Don't loop on it. Finish the job with the other sources, say plainly that Reddit is a gap in this research, and never stand in a summary's paraphrase for a Reddit quote."
93808
+ };
93809
+ }
93810
+ function failValidation4(message) {
93811
+ writeResearchJson({ ok: false, error: { code: "VALIDATION_ERROR", message, fix: redditFix("VALIDATION_ERROR") } });
93812
+ process.exit(1);
93813
+ }
93814
+ function subredditsFromArgs(rawArgs, parsed) {
93815
+ return repeatedValues(rawArgs, "subreddit", parsed).flatMap((value2) => value2.split(",")).map((name) => name.trim()).filter(Boolean);
93816
+ }
93817
+ var searchCommand3 = defineCommand225({
93818
+ meta: {
93819
+ name: "search",
93820
+ description: `Search Reddit for the audience's own words, verbatim, each with a permalink you can cite.
93821
+
93822
+ Start here for Reddit voice-of-customer. Matches words, not meaning \u2014 search the way the audience writes, and quote a phrase to keep it together ('"right to work"').
93823
+
93824
+ Examples:
93825
+ baker research reddit search "agency never paid" --subreddit couriersofreddit,AmazonFlexUK
93826
+ baker research reddit search '"right to work"' --subreddit UKJobs,AmazonFlexUK
93827
+ baker research reddit search "visa" --subreddit UberEATS --kind comments --since 2025-01-01
93828
+ baker research reddit search "side hustle student deliveries"`
93829
+ },
93830
+ args: {
93831
+ query: { type: "positional", description: "Words to match", required: false },
93832
+ subreddit: {
93833
+ type: "string",
93834
+ description: "Subreddits (up to 10): comma-separated, or repeat the flag",
93835
+ required: false
93836
+ },
93837
+ kind: { type: "string", description: "posts (default) or comments", required: false },
93838
+ since: { type: "string", description: "YYYY-MM-DD", required: false },
93839
+ limit: { type: "string", description: "1-50, default 25", required: false },
93840
+ full: { type: "boolean", description: "Whole texts, not previews", required: false }
93841
+ },
93842
+ run: async ({ args: args8, rawArgs }) => {
93843
+ const query = args8.query?.trim();
93844
+ if (!query) failValidation4('Say what to search for: baker research reddit search "<words>"');
93845
+ const kind = args8.kind ?? "posts";
93846
+ if (kind !== "posts" && kind !== "comments") failValidation4("--kind must be posts or comments");
93847
+ const body = {
93848
+ query,
93849
+ kind,
93850
+ subreddits: subredditsFromArgs(rawArgs, args8.subreddit)
93851
+ };
93852
+ if (args8.since) body.since = args8.since;
93853
+ if (args8.limit) body.limit = Number(args8.limit);
93854
+ try {
93855
+ const data = await apiPost("/api/research/reddit/search", body, {
93856
+ timeoutMs: TIMEOUT_MS2
93857
+ });
93858
+ writeResearchJson({
93859
+ ok: true,
93860
+ data: data.results.map((item) => row(item, Boolean(args8.full))),
93861
+ source: data.source,
93862
+ fields: ITEM_FIELDS,
93863
+ hints: redditSearchHints({ resultCount: data.results.length, kind, source: data.source })
93864
+ });
93865
+ } catch (err) {
93866
+ handleResearchError(err, redditFix);
93867
+ }
93868
+ }
93869
+ });
93870
+ var threadCommand = defineCommand225({
93871
+ meta: {
93872
+ name: "thread",
93873
+ description: `Read one Reddit thread verbatim \u2014 the post and its best comments, each with its own permalink.
93874
+
93875
+ Run it on every Reddit link before you quote from it, including links a research summary gave you: it refuses a link whose post is somewhere other than the link claims.
93876
+
93877
+ Examples:
93878
+ baker research reddit thread "https://www.reddit.com/r/AmazonFlexUK/comments/1abc2de/agency_still_hasnt_paid_me/"
93879
+ baker research reddit thread "https://www.reddit.com/r/UberEATS/s/3TzXiyxaMD" --comments 100
93880
+ baker research reddit thread 1abc2de --full`
93881
+ },
93882
+ args: {
93883
+ target: { type: "positional", description: "Thread link or post id", required: false },
93884
+ comments: { type: "string", description: "0-200, default 40", required: false },
93885
+ full: { type: "boolean", description: "Whole texts, not previews", required: false }
93886
+ },
93887
+ run: async ({ args: args8 }) => {
93888
+ const target2 = args8.target?.trim();
93889
+ if (!target2) failValidation4("Pass the thread's link or post id: baker research reddit thread <url>");
93890
+ const commentsAsked = args8.comments ? Number(args8.comments) : RESEARCH_REDDIT_THREAD_DEFAULT_COMMENTS;
93891
+ const body = { target: target2, comments: commentsAsked };
93892
+ try {
93893
+ const data = await apiPost("/api/research/reddit/thread", body, {
93894
+ timeoutMs: TIMEOUT_MS2
93895
+ });
93896
+ const full = Boolean(args8.full);
93897
+ writeResearchJson({
93898
+ ok: true,
93899
+ data: { post: row(data.post, full), comments: data.comments.map((item) => row(item, full)) },
93900
+ source: data.source,
93901
+ fields: ITEM_FIELDS,
93902
+ hints: redditThreadHints({
93903
+ commentsShown: data.comments.length,
93904
+ commentsAsked,
93905
+ commentCount: data.post.commentCount,
93906
+ source: data.source
93907
+ })
93908
+ });
93909
+ } catch (err) {
93910
+ handleResearchError(err, redditFix);
93911
+ }
93912
+ }
93913
+ });
93914
+ var subredditsCommand = defineCommand225({
93915
+ meta: {
93916
+ name: "subreddits",
93917
+ description: `Find the subreddits where an audience talks \u2014 name, members, and whether it can be read.
93918
+
93919
+ Run it before \`reddit search\` instead of guessing names: a plausible-sounding subreddit can be dead (r/CouriersUK has one post), while the conversation sits in r/AmazonFlexUK or r/deliveroos.
93920
+
93921
+ Examples:
93922
+ baker research reddit subreddits "amazon flex uk"
93923
+ baker research reddit subreddits "deliveroo" --limit 5`
93924
+ },
93925
+ args: {
93926
+ query: { type: "positional", description: "Audience or topic", required: false },
93927
+ limit: { type: "string", description: "1-25, default 10", required: false }
93928
+ },
93929
+ run: async ({ args: args8 }) => {
93930
+ const query = args8.query?.trim();
93931
+ if (!query) failValidation4('Say what to look for: baker research reddit subreddits "<audience or topic>"');
93932
+ const body = { query };
93933
+ if (args8.limit) body.limit = Number(args8.limit);
93934
+ try {
93935
+ const data = await apiPost("/api/research/reddit/subreddits", body, {
93936
+ timeoutMs: TIMEOUT_MS2
93937
+ });
93938
+ writeResearchJson({
93939
+ ok: true,
93940
+ data: data.results,
93941
+ source: data.source,
93942
+ fields: {
93943
+ name: "Pass this to --subreddit",
93944
+ subscribers: "Members \u2014 size, not activity",
93945
+ access: "public / restricted (readable, members post) / private (unreadable) / null when the source doesn't say",
93946
+ description: "The subreddit's own summary",
93947
+ url: "The subreddit"
93948
+ },
93949
+ hints: redditSubredditsHints({ resultCount: data.results.length })
93950
+ });
93951
+ } catch (err) {
93952
+ handleResearchError(err, redditFix);
93953
+ }
93954
+ }
93955
+ });
93956
+ var redditCommand = defineCommand225({
93957
+ meta: {
93958
+ name: "reddit",
93959
+ description: `Read Reddit verbatim \u2014 the one site no page reader will open for us.
93960
+
93961
+ Commands:
93962
+ subreddits \u2014 Find where the audience talks (start here)
93963
+ search \u2014 Find posts or comments in the audience's own words
93964
+ thread \u2014 Read one thread: the post and its best comments
93965
+
93966
+ Examples:
93967
+ baker research reddit subreddits "amazon flex uk"
93968
+ baker research reddit search "agency never paid" --subreddit couriersofreddit,AmazonFlexUK --kind comments
93969
+ baker research reddit thread "https://www.reddit.com/r/AmazonFlexUK/comments/1abc2de/"
93970
+ Full guide: __tooling__/docs/tools/baker/research.md`
93971
+ },
93972
+ subCommands: { subreddits: subredditsCommand, search: searchCommand3, thread: threadCommand }
93973
+ });
93974
+
93975
+ // src/commands/research/relevant-pages.ts
93976
+ import { defineCommand as defineCommand226 } from "citty";
93568
93977
  registerSchema({
93569
93978
  command: "research.relevant-pages",
93570
93979
  description: "Get the top pages of a competitor domain with organic traffic and ranking data. Shows which pages drive the most traffic. If --location and --language are omitted, defaults to United States (us) and English (en).",
@@ -93590,7 +93999,7 @@ var FIELDS12 = {
93590
93999
  keywords: "Total organic keywords the page ranks for",
93591
94000
  top_10: "Keywords in positions 1-10"
93592
94001
  };
93593
- var relevantPagesCommand = defineCommand225({
94002
+ var relevantPagesCommand = defineCommand226({
93594
94003
  meta: {
93595
94004
  name: "relevant-pages",
93596
94005
  description: `Get the top pages of a competitor domain with traffic data.
@@ -93637,7 +94046,7 @@ Examples:
93637
94046
  });
93638
94047
 
93639
94048
  // src/commands/research/web.ts
93640
- import { defineCommand as defineCommand226 } from "citty";
94049
+ import { defineCommand as defineCommand227 } from "citty";
93641
94050
  registerSchema({
93642
94051
  command: "research.web",
93643
94052
  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).",
@@ -93655,7 +94064,7 @@ registerSchema({
93655
94064
  }
93656
94065
  }
93657
94066
  });
93658
- var TIMEOUT_MS2 = {
94067
+ var TIMEOUT_MS3 = {
93659
94068
  medium: 18e4,
93660
94069
  high: 36e4,
93661
94070
  xhigh: 9e5
@@ -93671,7 +94080,7 @@ async function runDeepResearch(question) {
93671
94080
  const { interactionId } = await apiPost("/api/research/deep-research/start", { question });
93672
94081
  process.stderr.write(`Deep research started (${interactionId}). Polling...
93673
94082
  `);
93674
- const deadline = Date.now() + TIMEOUT_MS2.xhigh;
94083
+ const deadline = Date.now() + TIMEOUT_MS3.xhigh;
93675
94084
  while (Date.now() < deadline) {
93676
94085
  await sleep3(1e4);
93677
94086
  const poll = await apiGet("/api/research/deep-research/poll", {
@@ -93688,7 +94097,7 @@ async function runDeepResearch(question) {
93688
94097
  }
93689
94098
  throw new Error("Deep research timed out");
93690
94099
  }
93691
- var webCommand = defineCommand226({
94100
+ var webCommand = defineCommand227({
93692
94101
  meta: {
93693
94102
  name: "web",
93694
94103
  description: `Search the web with AI to answer any open-ended marketing question. Uses live internet data via Google Search.
@@ -93726,7 +94135,7 @@ Examples:
93726
94135
  process.exit(1);
93727
94136
  }
93728
94137
  try {
93729
- const data = depth === "xhigh" ? await runDeepResearch(question) : await apiPost("/api/research/explore", { question, depth }, { timeoutMs: TIMEOUT_MS2[depth] });
94138
+ const data = depth === "xhigh" ? await runDeepResearch(question) : await apiPost("/api/research/explore", { question, depth }, { timeoutMs: TIMEOUT_MS3[depth] });
93730
94139
  const format = args8.output || "json";
93731
94140
  const hints2 = webDepthHints({ answer: data.answer, sourceCount: data.sources.length, depth });
93732
94141
  if (format === "md") {
@@ -93750,7 +94159,7 @@ Examples:
93750
94159
  });
93751
94160
 
93752
94161
  // src/commands/research/index.ts
93753
- var researchCommand = defineCommand227({
94162
+ var researchCommand = defineCommand228({
93754
94163
  meta: {
93755
94164
  name: "research",
93756
94165
  description: `Competitive intelligence and AI-powered research commands.
@@ -93758,6 +94167,7 @@ var researchCommand = defineCommand227({
93758
94167
  Commands:
93759
94168
  web \u2014 Search the web with AI (ask any question)
93760
94169
  fetch \u2014 Read a page an ordinary web fetch could not
94170
+ reddit \u2014 Read Reddit verbatim: search posts/comments, read a thread
93761
94171
  advertisers \u2014 Who's bidding on a keyword?
93762
94172
  autocomplete \u2014 Google Autocomplete suggestions
93763
94173
  intent \u2014 Google Search intent classification
@@ -93773,6 +94183,7 @@ Examples:
93773
94183
  baker research web "Who are the main competitors of HubSpot?"
93774
94184
  baker research web "What is the ICP for CRM buyers?" --depth high
93775
94185
  baker research fetch "https://competitor.com/pricing"
94186
+ baker research reddit search "agency never paid" --subreddit AmazonFlexUK --kind comments
93776
94187
  baker research advertisers "running shoes"
93777
94188
  baker research intent "buy running shoes,best running shoes 2026"
93778
94189
  baker research keyword-metrics "chatbot whatsapp,crm whatsapp" --location mx --language es
@@ -93783,6 +94194,7 @@ Full guide: __tooling__/docs/tools/baker/research.md`
93783
94194
  subCommands: {
93784
94195
  web: webCommand,
93785
94196
  fetch: fetchCommand,
94197
+ reddit: redditCommand,
93786
94198
  advertisers: advertisersCommand,
93787
94199
  autocomplete: autocompleteCommand,
93788
94200
  countries: countriesCommand,
@@ -93797,10 +94209,10 @@ Full guide: __tooling__/docs/tools/baker/research.md`
93797
94209
  });
93798
94210
 
93799
94211
  // src/commands/scheduled-agents/index.ts
93800
- import { defineCommand as defineCommand238 } from "citty";
94212
+ import { defineCommand as defineCommand239 } from "citty";
93801
94213
 
93802
94214
  // src/commands/scheduled-agents/create.ts
93803
- import { defineCommand as defineCommand228 } from "citty";
94215
+ import { defineCommand as defineCommand229 } from "citty";
93804
94216
 
93805
94217
  // src/commands/scheduled-agents/shared.ts
93806
94218
  var TEMP_SCHEDULED_AGENT_PREFIX = "temp_agent_";
@@ -93808,7 +94220,7 @@ var TEMP_ID_PREFIX = "temp_";
93808
94220
  function writeOk2(data, hints2) {
93809
94221
  writeJson({ ok: true, data: data ?? null, ...hints2 && hints2.length > 0 ? { hints: hints2 } : {} });
93810
94222
  }
93811
- function failValidation4(message) {
94223
+ function failValidation5(message) {
93812
94224
  writeJson({ ok: false, error: { code: "VALIDATION_ERROR", message } });
93813
94225
  process.exit(1);
93814
94226
  }
@@ -93856,7 +94268,7 @@ function isOtherTempId(id) {
93856
94268
  }
93857
94269
  function validateScheduledAgentRef(ref2) {
93858
94270
  if (isOtherTempId(ref2)) {
93859
- failValidation4(`Scheduled agent temp IDs must start with ${TEMP_SCHEDULED_AGENT_PREFIX}.`);
94271
+ failValidation5(`Scheduled agent temp IDs must start with ${TEMP_SCHEDULED_AGENT_PREFIX}.`);
93860
94272
  }
93861
94273
  if (!isTempScheduledAgentId(ref2)) {
93862
94274
  validateConvexId(ref2);
@@ -93903,14 +94315,14 @@ var NARROWING_ARGS = {
93903
94315
  };
93904
94316
  function validateRunAt(runAt) {
93905
94317
  if (!runAt.endsWith("Z")) {
93906
- failValidation4("--run-at must be an ISO UTC timestamp ending in Z.");
94318
+ failValidation5("--run-at must be an ISO UTC timestamp ending in Z.");
93907
94319
  }
93908
94320
  const parsed = Date.parse(runAt);
93909
94321
  if (!Number.isFinite(parsed)) {
93910
- failValidation4("--run-at must be a valid ISO timestamp.");
94322
+ failValidation5("--run-at must be a valid ISO timestamp.");
93911
94323
  }
93912
94324
  if (parsed <= Date.now()) {
93913
- failValidation4(
94325
+ failValidation5(
93914
94326
  `--run-at ${runAt} has already passed. A one-off item only runs at a time still to come \u2014 pass a later ISO UTC timestamp, and check which timezone you converted from if you worked it out from a local time.`
93915
94327
  );
93916
94328
  }
@@ -93922,7 +94334,7 @@ function buildTriggerBody(args8, options) {
93922
94334
  const kind = args8.trigger;
93923
94335
  const given = [cron, runAt, kind].filter(Boolean).length;
93924
94336
  if (given > 1) {
93925
- failValidation4("--cron, --run-at and --trigger are mutually exclusive.");
94337
+ failValidation5("--cron, --run-at and --trigger are mutually exclusive.");
93926
94338
  }
93927
94339
  refuseNarrowingWithoutKind(args8, kind, given);
93928
94340
  if (runAt) {
@@ -93939,7 +94351,7 @@ function buildTriggerBody(args8, options) {
93939
94351
  return { timezone };
93940
94352
  }
93941
94353
  if (options.required) {
93942
- failValidation4(
94354
+ failValidation5(
93943
94355
  `A trigger is required. Pass --cron or --run-at for a time-based one, or --trigger with one of: ${TRIGGER_KIND_IDS.join(", ")}.`
93944
94356
  );
93945
94357
  }
@@ -93951,13 +94363,13 @@ function flagText(args8, key) {
93951
94363
  }
93952
94364
  function refuseMisusedKinds(kind, args8) {
93953
94365
  if (kind === "time.once") {
93954
- failValidation4("Use --run-at for a one-off, rather than --trigger time.once.");
94366
+ failValidation5("Use --run-at for a one-off, rather than --trigger time.once.");
93955
94367
  }
93956
94368
  if (kind === "time.cron") {
93957
- failValidation4("Use --cron for a repeating schedule, rather than --trigger time.cron.");
94369
+ failValidation5("Use --cron for a repeating schedule, rather than --trigger time.cron.");
93958
94370
  }
93959
94371
  if (kind === "experiment.started" && flagText(args8, "experiment")) {
93960
- failValidation4(
94372
+ failValidation5(
93961
94373
  'A test that exists has already started, so --experiment never fires on experiment.started. Leave it off and add --when "page = <page>" to watch one page.'
93962
94374
  );
93963
94375
  }
@@ -93970,7 +94382,7 @@ function refuseStrayFlags(kind, args8) {
93970
94382
  if (stray.length === 0) {
93971
94383
  return;
93972
94384
  }
93973
- failValidation4(
94385
+ failValidation5(
93974
94386
  `${stray.map((flag) => `--${flag}`).join(", ")} does not narrow ${kind}. ${allowed.size > 0 ? `It can be pointed with ${[...allowed].map((flag) => `--${flag}`).join(", ")}.` : "It runs every time it happens \u2014 narrow it with --when rules over its payload instead."}`
93975
94387
  );
93976
94388
  }
@@ -93984,7 +94396,7 @@ function refuseNarrowingWithoutKind(args8, kind, given) {
93984
94396
  return;
93985
94397
  }
93986
94398
  const flags = stray.map((flag) => `--${flag}`).join(", ");
93987
- failValidation4(
94399
+ failValidation5(
93988
94400
  given > 0 ? `${flags} does not narrow --cron or --run-at: a time trigger runs every time it comes round. Use --trigger <kind> with ${flags} instead, or leave ${flags} off.` : `${flags} narrows a trigger, and no trigger was given. Pass --trigger <kind> too \u2014 the item's whole trigger, with ${flags} \u2014 or leave ${flags} off.`
93989
94401
  );
93990
94402
  }
@@ -93994,7 +94406,7 @@ function configFromFlag(kind, args8) {
93994
94406
  return {};
93995
94407
  }
93996
94408
  if (kind !== "app.event") {
93997
- failValidation4("--config is only for --trigger app.event: the settings of what happens in the tool.");
94409
+ failValidation5("--config is only for --trigger app.event: the settings of what happens in the tool.");
93998
94410
  }
93999
94411
  let parsed;
94000
94412
  try {
@@ -94003,13 +94415,13 @@ function configFromFlag(kind, args8) {
94003
94415
  parsed = null;
94004
94416
  }
94005
94417
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
94006
- failValidation4(`--config must be a JSON object, e.g. '{"labelIds":"INBOX"}'.`);
94418
+ failValidation5(`--config must be a JSON object, e.g. '{"labelIds":"INBOX"}'.`);
94007
94419
  }
94008
94420
  return { configJson: config };
94009
94421
  }
94010
94422
  function triggerFromFlags(kind, args8) {
94011
94423
  if (!isTriggerKind(kind)) {
94012
- failValidation4(`--trigger must be one of: ${TRIGGER_KIND_IDS.join(", ")}.`);
94424
+ failValidation5(`--trigger must be one of: ${TRIGGER_KIND_IDS.join(", ")}.`);
94013
94425
  }
94014
94426
  refuseMisusedKinds(kind, args8);
94015
94427
  refuseStrayFlags(kind, args8);
@@ -94019,7 +94431,7 @@ function triggerFromFlags(kind, args8) {
94019
94431
  if (value2) {
94020
94432
  narrowings[field] = value2;
94021
94433
  } else if (required) {
94022
- failValidation4(
94434
+ failValidation5(
94023
94435
  `--trigger ${kind} needs --${TRIGGER_TARGET_FIELDS[field].flag}. \`baker scheduled-agents targets --trigger ${kind} --field ${field}\` lists what it can be.`
94024
94436
  );
94025
94437
  }
@@ -94031,12 +94443,12 @@ function parseOccurrences(raw) {
94031
94443
  return void 0;
94032
94444
  }
94033
94445
  if (typeof raw !== "string") {
94034
- failValidation4("--occurrences must be a comma-separated list of whole numbers, e.g. 1 or 1,3.");
94446
+ failValidation5("--occurrences must be a comma-separated list of whole numbers, e.g. 1 or 1,3.");
94035
94447
  }
94036
94448
  const parts = raw.split(",").map((part) => part.trim()).filter((part) => part.length > 0);
94037
94449
  const numbers = parts.map((part) => Number(part));
94038
94450
  if (numbers.length === 0 || numbers.some((n) => !Number.isInteger(n) || n <= 0)) {
94039
- failValidation4("--occurrences must be a comma-separated list of whole numbers above zero, e.g. 1 or 1,3.");
94451
+ failValidation5("--occurrences must be a comma-separated list of whole numbers above zero, e.g. 1 or 1,3.");
94040
94452
  }
94041
94453
  return numbers;
94042
94454
  }
@@ -94050,7 +94462,7 @@ function parseBooleanFlag(raw, flagName) {
94050
94462
  if (raw === false || raw === "false") {
94051
94463
  return false;
94052
94464
  }
94053
- failValidation4(`${flagName} must be true or false.`);
94465
+ failValidation5(`${flagName} must be true or false.`);
94054
94466
  }
94055
94467
  var REACTION_ARGS = {
94056
94468
  when: {
@@ -94080,7 +94492,7 @@ function wholeNumber(raw, flag, { min }) {
94080
94492
  }
94081
94493
  const value2 = Number(raw);
94082
94494
  if (!Number.isInteger(value2) || value2 < min) {
94083
- failValidation4(`${flag} must be a whole number of at least ${min}.`);
94495
+ failValidation5(`${flag} must be a whole number of at least ${min}.`);
94084
94496
  }
94085
94497
  return value2;
94086
94498
  }
@@ -94100,7 +94512,7 @@ function occurrencesFromFlags(args8) {
94100
94512
  const times = wholeNumber(args8.times, "--times", { min: 1 });
94101
94513
  const occurrences = parseOccurrences(args8.occurrences);
94102
94514
  if (times !== void 0 && occurrences !== void 0) {
94103
- failValidation4("Pass --times or --occurrences, not both \u2014 --times 3 is --occurrences 1,2,3.");
94515
+ failValidation5("Pass --times or --occurrences, not both \u2014 --times 3 is --occurrences 1,2,3.");
94104
94516
  }
94105
94517
  return times !== void 0 ? firstTimes(times) : occurrences;
94106
94518
  }
@@ -94114,7 +94526,7 @@ function whenFromFlag(raw) {
94114
94526
  }
94115
94527
  const parsed = parseConditionsExpression(text2);
94116
94528
  if (!parsed.ok) {
94117
- failValidation4(parsed.error);
94529
+ failValidation5(parsed.error);
94118
94530
  }
94119
94531
  return text2;
94120
94532
  }
@@ -94123,7 +94535,7 @@ function payloadFromFlag(raw) {
94123
94535
  return void 0;
94124
94536
  }
94125
94537
  if (raw !== "include" && raw !== "omit") {
94126
- failValidation4("--payload must be include or omit.");
94538
+ failValidation5("--payload must be include or omit.");
94127
94539
  }
94128
94540
  return raw === "include";
94129
94541
  }
@@ -94167,7 +94579,7 @@ registerSchema({
94167
94579
  }
94168
94580
  }
94169
94581
  });
94170
- var createCommand3 = defineCommand228({
94582
+ var createCommand3 = defineCommand229({
94171
94583
  meta: {
94172
94584
  name: "create",
94173
94585
  description: 'Stage a scheduled agent. Examples: baker scheduled-agents create --name "Weekly report" --description "..." --prompt "..." --cron "0 9 * * MON" | baker scheduled-agents create --name "Chase a new lead" --description "..." --prompt "Research {{payload.answers.company}} and draft a reply" --trigger form.submitted --form quote | baker scheduled-agents create --name "Big deals" --description "..." --prompt "..." --trigger webhook.received --when "deal.stage = won AND deal.amount > 10000" --times 3'
@@ -94195,13 +94607,13 @@ var createCommand3 = defineCommand228({
94195
94607
  const prompt = args8.prompt;
94196
94608
  const template = typeof args8.template === "string" ? args8.template.trim() : "";
94197
94609
  if (!name || name.trim().length === 0) {
94198
- failValidation4("--name is required.");
94610
+ failValidation5("--name is required.");
94199
94611
  }
94200
94612
  if (!description || description.trim().length === 0) {
94201
- failValidation4("--description is required.");
94613
+ failValidation5("--description is required.");
94202
94614
  }
94203
94615
  if (template.length === 0 && (!prompt || prompt.trim().length === 0)) {
94204
- failValidation4("--prompt is required: it is the whole of what a run does. Or pass --template to copy a brief.");
94616
+ failValidation5("--prompt is required: it is the whole of what a run does. Or pass --template to copy a brief.");
94205
94617
  }
94206
94618
  const trigger = buildTriggerBody(args8, { required: true });
94207
94619
  const reaction = reactionBody(args8);
@@ -94224,7 +94636,7 @@ var createCommand3 = defineCommand228({
94224
94636
  });
94225
94637
 
94226
94638
  // src/commands/scheduled-agents/delete.ts
94227
- import { defineCommand as defineCommand229 } from "citty";
94639
+ import { defineCommand as defineCommand230 } from "citty";
94228
94640
  registerSchema({
94229
94641
  command: "scheduled-agents.delete",
94230
94642
  description: "Stage deletion of a published scheduled agent, or cancel one this chat staged (temp_agent_*).",
@@ -94232,7 +94644,7 @@ registerSchema({
94232
94644
  id: { type: "string", description: "Published scheduled agent ID or temp_agent_* draft ID", required: true }
94233
94645
  }
94234
94646
  });
94235
- var deleteCommand3 = defineCommand229({
94647
+ var deleteCommand3 = defineCommand230({
94236
94648
  meta: {
94237
94649
  name: "delete",
94238
94650
  description: "Stage a scheduled agent deletion. Example: baker scheduled-agents delete <id-or-temp_agent_id>"
@@ -94249,7 +94661,7 @@ var deleteCommand3 = defineCommand229({
94249
94661
  try {
94250
94662
  const id = args8.id || args8["scheduled-agent-id"];
94251
94663
  if (!id) {
94252
- failValidation4("Scheduled agent ID is required.");
94664
+ failValidation5("Scheduled agent ID is required.");
94253
94665
  }
94254
94666
  validateScheduledAgentRef(id);
94255
94667
  await apiPost("/api/scheduled-agents/delete", { chatId: requireChatId(), id });
@@ -94261,7 +94673,7 @@ var deleteCommand3 = defineCommand229({
94261
94673
  });
94262
94674
 
94263
94675
  // src/commands/scheduled-agents/get.ts
94264
- import { defineCommand as defineCommand230 } from "citty";
94676
+ import { defineCommand as defineCommand231 } from "citty";
94265
94677
  registerSchema({
94266
94678
  command: "scheduled-agents.get",
94267
94679
  description: "Get a published scheduled agent, or one this chat has staged (temp_agent_*). A published one comes with runsAs: who each run acts as ({ name, personalTools, companyTools, notSignedIn?, toolsOff? }) \u2014 the tools a run loads, which decides whether a job like reading someone's inbox can work. Its recentRuns and any draftOps this chat staged on it come with it.",
@@ -94270,7 +94682,7 @@ registerSchema({
94270
94682
  chat: { type: "string", description: CHAT_READ_ARG.description, required: false }
94271
94683
  }
94272
94684
  });
94273
- var getCommand4 = defineCommand230({
94685
+ var getCommand4 = defineCommand231({
94274
94686
  meta: {
94275
94687
  name: "get",
94276
94688
  description: "Get a scheduled agent. Example: baker scheduled-agents get <id-or-temp_agent_id>"
@@ -94288,13 +94700,13 @@ var getCommand4 = defineCommand230({
94288
94700
  try {
94289
94701
  const id = args8.id || args8["scheduled-agent-id"];
94290
94702
  if (!id) {
94291
- failValidation4("Scheduled agent ID is required.");
94703
+ failValidation5("Scheduled agent ID is required.");
94292
94704
  }
94293
94705
  validateScheduledAgentRef(id);
94294
94706
  const env = getEnv();
94295
94707
  const chatId = typeof args8.chat === "string" && args8.chat.length > 0 ? args8.chat : env.BAKER_CHAT_ID;
94296
94708
  if (isTempScheduledAgentId(id) && !chatId) {
94297
- failValidation4("BAKER_CHAT_ID or --chat is required to get a staged scheduled agent ID.");
94709
+ failValidation5("BAKER_CHAT_ID or --chat is required to get a staged scheduled agent ID.");
94298
94710
  }
94299
94711
  const body = { id };
94300
94712
  if (chatId) {
@@ -94309,7 +94721,7 @@ var getCommand4 = defineCommand230({
94309
94721
  });
94310
94722
 
94311
94723
  // src/commands/scheduled-agents/list.ts
94312
- import { defineCommand as defineCommand231 } from "citty";
94724
+ import { defineCommand as defineCommand232 } from "citty";
94313
94725
  var FULL_DESCRIPTION2 = "Return every item and staged change as stored, briefs included (tens of KB). Prefer `get <id>` for one item.";
94314
94726
  registerSchema({
94315
94727
  command: "scheduled-agents.list",
@@ -94319,7 +94731,7 @@ registerSchema({
94319
94731
  full: { type: "boolean", description: FULL_DESCRIPTION2, required: false }
94320
94732
  }
94321
94733
  });
94322
- var listCommand19 = defineCommand231({
94734
+ var listCommand19 = defineCommand232({
94323
94735
  meta: {
94324
94736
  name: "list",
94325
94737
  description: "List scheduled agents: id, name, trigger and status for each, plus this chat's staged changes when BAKER_CHAT_ID is set (or --chat <id> for an earlier chat's). Briefs are left out. Start here: baker scheduled-agents list; then baker scheduled-agents get <id> for one item's brief."
@@ -94345,7 +94757,7 @@ var listCommand19 = defineCommand231({
94345
94757
  });
94346
94758
 
94347
94759
  // src/commands/scheduled-agents/run.ts
94348
- import { defineCommand as defineCommand232 } from "citty";
94760
+ import { defineCommand as defineCommand233 } from "citty";
94349
94761
  registerSchema({
94350
94762
  command: "scheduled-agents.run",
94351
94763
  description: "Run a published scheduled agent now, whatever its trigger says. Opens a Session immediately and returns its chatId. Does not need BAKER_CHAT_ID, and refuses a temp_agent_* id \u2014 a staged item has to be published before it can run.",
@@ -94353,7 +94765,7 @@ registerSchema({
94353
94765
  id: { type: "string", description: "Published scheduled agent ID", required: true }
94354
94766
  }
94355
94767
  });
94356
- var runCommand2 = defineCommand232({
94768
+ var runCommand2 = defineCommand233({
94357
94769
  meta: {
94358
94770
  name: "run",
94359
94771
  description: "Run a published scheduled agent now. Example: baker scheduled-agents run <id>"
@@ -94370,10 +94782,10 @@ var runCommand2 = defineCommand232({
94370
94782
  try {
94371
94783
  const id = args8.id || args8["scheduled-agent-id"];
94372
94784
  if (!id) {
94373
- failValidation4("Scheduled agent ID is required.");
94785
+ failValidation5("Scheduled agent ID is required.");
94374
94786
  }
94375
94787
  if (isTempScheduledAgentId(id)) {
94376
- failValidation4("run needs a published scheduled agent ID, not temp_agent_*. Publish this chat first.");
94788
+ failValidation5("run needs a published scheduled agent ID, not temp_agent_*. Publish this chat first.");
94377
94789
  }
94378
94790
  validateConvexId(id);
94379
94791
  const response = await apiPost("/api/scheduled-agents/run", { id });
@@ -94385,7 +94797,7 @@ var runCommand2 = defineCommand232({
94385
94797
  });
94386
94798
 
94387
94799
  // src/commands/scheduled-agents/targets.ts
94388
- import { defineCommand as defineCommand233 } from "citty";
94800
+ import { defineCommand as defineCommand234 } from "citty";
94389
94801
  var ARGS9 = {
94390
94802
  trigger: {
94391
94803
  type: "string",
@@ -94419,7 +94831,7 @@ registerSchema({
94419
94831
  description: "What a trigger can be pointed at: the open Tasks, the company's scheduled agents, its review sources, followed competitors, pages, Forms and their steps, Knowledge collections, tools. Returns each option's value (pass it to the matching flag on create/update) with its name.",
94420
94832
  args: ARGS9
94421
94833
  });
94422
- var targetsCommand = defineCommand233({
94834
+ var targetsCommand = defineCommand234({
94423
94835
  meta: {
94424
94836
  name: "targets",
94425
94837
  description: `What a trigger can be pointed at \u2014 so "when a Task is picked up" becomes "when THIS Task is picked up".
@@ -94440,7 +94852,7 @@ Full guide: __tooling__/docs/tools/baker/scheduled-agents.md`
94440
94852
  run: async ({ args: args8 }) => {
94441
94853
  const trigger = args8.trigger;
94442
94854
  if (!TRIGGER_KIND_IDS.some((kind) => kind === trigger)) {
94443
- failValidation4(`--trigger must be one of: ${TRIGGER_KIND_IDS.join(", ")}.`);
94855
+ failValidation5(`--trigger must be one of: ${TRIGGER_KIND_IDS.join(", ")}.`);
94444
94856
  }
94445
94857
  try {
94446
94858
  const response = await apiPost("/api/scheduled-agents/targets", {
@@ -94462,7 +94874,7 @@ Full guide: __tooling__/docs/tools/baker/scheduled-agents.md`
94462
94874
  // src/commands/scheduled-agents/templates.ts
94463
94875
  import { readFile as readFile42 } from "fs/promises";
94464
94876
  import path55 from "path";
94465
- import { defineCommand as defineCommand234 } from "citty";
94877
+ import { defineCommand as defineCommand235 } from "citty";
94466
94878
  registerSchema({
94467
94879
  command: "scheduled-agents.templates",
94468
94880
  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.",
@@ -94517,7 +94929,7 @@ registerSchema({
94517
94929
  }
94518
94930
  }
94519
94931
  });
94520
- var templatesCommand = defineCommand234({
94932
+ var templatesCommand = defineCommand235({
94521
94933
  meta: {
94522
94934
  name: "templates",
94523
94935
  description: `The recipes this company can run \u2014 Baker's own plus theirs, with what each one produces.
@@ -94564,13 +94976,13 @@ Full guide: __tooling__/docs/tools/baker/scheduled-agents.md`
94564
94976
  const publish = flag("publish");
94565
94977
  const remove = flag("delete");
94566
94978
  if ([save, publish, remove].filter((id) => id.length > 0).length > 1) {
94567
- failValidation4("Pass one of --save, --publish or --delete at a time.");
94979
+ failValidation5("Pass one of --save, --publish or --delete at a time.");
94568
94980
  }
94569
94981
  if (save.length > 0) {
94570
94982
  const briefFile = flag("brief-file");
94571
94983
  const brief2 = briefFile.length > 0 ? await readFile42(path55.resolve(briefFile), "utf8") : flag("brief");
94572
94984
  if (brief2.trim().length === 0) {
94573
- failValidation4("--brief-file (preferred) or --brief is required: the brief is the recipe.");
94985
+ failValidation5("--brief-file (preferred) or --brief is required: the brief is the recipe.");
94574
94986
  }
94575
94987
  const response2 = await apiPost("/api/marketplace/save", {
94576
94988
  chatId: requireChatId(),
@@ -94613,7 +95025,7 @@ Full guide: __tooling__/docs/tools/baker/scheduled-agents.md`
94613
95025
  });
94614
95026
 
94615
95027
  // src/commands/scheduled-agents/triggers.ts
94616
- import { defineCommand as defineCommand235 } from "citty";
95028
+ import { defineCommand as defineCommand236 } from "citty";
94617
95029
  var ARGS10 = {
94618
95030
  trigger: {
94619
95031
  type: "string",
@@ -94699,7 +95111,7 @@ function shapeTriggerCatalog(response, filter) {
94699
95111
  }
94700
95112
  return { ok: true, data: { groups, reacting: catalog.reacting } };
94701
95113
  }
94702
- var triggersCommand2 = defineCommand235({
95114
+ var triggersCommand2 = defineCommand236({
94703
95115
  meta: {
94704
95116
  name: "triggers",
94705
95117
  description: `Everything that can start a scheduled agent.
@@ -94729,7 +95141,7 @@ Full guide: __tooling__/docs/tools/baker/scheduled-agents.md`
94729
95141
  });
94730
95142
 
94731
95143
  // src/commands/scheduled-agents/unstage.ts
94732
- import { defineCommand as defineCommand236 } from "citty";
95144
+ import { defineCommand as defineCommand237 } from "citty";
94733
95145
  registerSchema({
94734
95146
  command: "scheduled-agents.unstage",
94735
95147
  description: "Take back what this chat staged on one scheduled agent, before Publish \u2014 the same as discarding its row in the change list. With --index, only that one change (its `index` in `baker scheduled-agents list` \u2192 draft.ops). Taking back a create also takes back the edits staged on it. Never touches a published item: to change or remove one, stage update/delete instead.",
@@ -94749,13 +95161,13 @@ function unstageBody(args8, chatId) {
94749
95161
  }
94750
95162
  const index = Number(args8.index);
94751
95163
  if (args8.index.trim() === "" || !Number.isInteger(index) || index < 0) {
94752
- failValidation4(
95164
+ failValidation5(
94753
95165
  "--index must be a whole number from `baker scheduled-agents list` \u2192 draft.ops[].index. Leave it off to take back everything staged on the item."
94754
95166
  );
94755
95167
  }
94756
95168
  return { chatId, ref: args8.ref, index };
94757
95169
  }
94758
- var unstageCommand = defineCommand236({
95170
+ var unstageCommand = defineCommand237({
94759
95171
  meta: {
94760
95172
  name: "unstage",
94761
95173
  description: "Take back what this chat staged on a scheduled agent, before Publish. Examples: baker scheduled-agents unstage temp_agent_abc123 | baker scheduled-agents unstage <id> --index 2"
@@ -94773,7 +95185,7 @@ var unstageCommand = defineCommand236({
94773
95185
  try {
94774
95186
  const ref2 = args8.id || args8["scheduled-agent-id"];
94775
95187
  if (!ref2) {
94776
- failValidation4("Scheduled agent ID is required.");
95188
+ failValidation5("Scheduled agent ID is required.");
94777
95189
  }
94778
95190
  const index = typeof args8.index === "string" ? args8.index : void 0;
94779
95191
  const body = unstageBody({ ref: ref2, ...index !== void 0 ? { index } : {} }, requireChatId());
@@ -94786,7 +95198,7 @@ var unstageCommand = defineCommand236({
94786
95198
  });
94787
95199
 
94788
95200
  // src/commands/scheduled-agents/update.ts
94789
- import { defineCommand as defineCommand237 } from "citty";
95201
+ import { defineCommand as defineCommand238 } from "citty";
94790
95202
  registerSchema({
94791
95203
  command: "scheduled-agents.update",
94792
95204
  description: "Stage an update to a published scheduled agent, or to one this chat staged (temp_agent_*).",
@@ -94813,7 +95225,7 @@ registerSchema({
94813
95225
  enabled: { type: "string", description: "Replacement enabled state", required: false, enum: ["true", "false"] }
94814
95226
  }
94815
95227
  });
94816
- var updateCommand4 = defineCommand237({
95228
+ var updateCommand4 = defineCommand238({
94817
95229
  meta: {
94818
95230
  name: "update",
94819
95231
  description: "Stage a scheduled agent update. Examples: baker scheduled-agents update <id> --enabled false | baker scheduled-agents update <id> --trigger form.submitted --form quote"
@@ -94843,7 +95255,7 @@ var updateCommand4 = defineCommand237({
94843
95255
  refuseAutoPublish(args8);
94844
95256
  const id = args8.id || args8["scheduled-agent-id"];
94845
95257
  if (!id) {
94846
- failValidation4("Scheduled agent ID is required.");
95258
+ failValidation5("Scheduled agent ID is required.");
94847
95259
  }
94848
95260
  validateScheduledAgentRef(id);
94849
95261
  const body = { id };
@@ -94870,7 +95282,7 @@ var updateCommand4 = defineCommand237({
94870
95282
  hasPatch = true;
94871
95283
  }
94872
95284
  if (!hasPatch) {
94873
- failValidation4(
95285
+ failValidation5(
94874
95286
  "Provide at least one of --name, --description, --prompt, --cron, --run-at, --trigger, --occurrences, --times, --when, --payload, --cooldown, --timezone, --enabled."
94875
95287
  );
94876
95288
  }
@@ -94884,7 +95296,7 @@ var updateCommand4 = defineCommand237({
94884
95296
  });
94885
95297
 
94886
95298
  // src/commands/scheduled-agents/index.ts
94887
- var scheduledAgentsCommand = defineCommand238({
95299
+ var scheduledAgentsCommand = defineCommand239({
94888
95300
  meta: {
94889
95301
  name: "scheduled-agents",
94890
95302
  description: `Manage Scheduled agents. Subcommands: list, get, triggers, targets, create, update, delete, unstage, run, templates.
@@ -94927,14 +95339,14 @@ Full guide: __tooling__/docs/tools/baker/scheduled-agents.md`
94927
95339
  });
94928
95340
 
94929
95341
  // src/commands/schema.ts
94930
- import { defineCommand as defineCommand239 } from "citty";
95342
+ import { defineCommand as defineCommand240 } from "citty";
94931
95343
  function narrowToFamily(commandName, available) {
94932
95344
  const segments = commandName.split(".");
94933
95345
  const prefix = segments[0] === "ads" && segments[1] ? `ads.${segments[1]}.` : `${segments[0]}.`;
94934
95346
  const siblings = available.filter((name) => name.startsWith(prefix));
94935
95347
  return siblings.length > 0 ? siblings : available;
94936
95348
  }
94937
- var schemaCommand2 = defineCommand239({
95349
+ var schemaCommand2 = defineCommand240({
94938
95350
  meta: {
94939
95351
  name: "schema",
94940
95352
  description: "Inspect command argument schemas (for AI agent introspection). Lists all commands if no argument given. Example: baker schema images search (or images.search)"
@@ -94979,10 +95391,10 @@ var schemaCommand2 = defineCommand239({
94979
95391
  });
94980
95392
 
94981
95393
  // src/commands/studio/index.ts
94982
- import { defineCommand as defineCommand248 } from "citty";
95394
+ import { defineCommand as defineCommand249 } from "citty";
94983
95395
 
94984
95396
  // src/commands/studio/animate.ts
94985
- import { defineCommand as defineCommand240 } from "citty";
95397
+ import { defineCommand as defineCommand241 } from "citty";
94986
95398
 
94987
95399
  // src/commands/studio/spokenPace.ts
94988
95400
  var WORDS_PER_SECOND = SPOKEN_WORDS_PER_SECOND;
@@ -95495,7 +95907,7 @@ async function adByHandHintFor(body) {
95495
95907
  return null;
95496
95908
  }
95497
95909
  }
95498
- var animateCommand = defineCommand240({
95910
+ var animateCommand = defineCommand241({
95499
95911
  meta: {
95500
95912
  name: "animate",
95501
95913
  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 'she looks to camera and says: \"Hola, soy Elena\"' --avatar elena --quality 720p --aspect-ratio 9:16\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"
@@ -95620,7 +96032,7 @@ var animateCommand = defineCommand240({
95620
96032
  });
95621
96033
 
95622
96034
  // src/commands/studio/generate.ts
95623
- import { defineCommand as defineCommand241 } from "citty";
96035
+ import { defineCommand as defineCommand242 } from "citty";
95624
96036
  var MODEL_LIST2 = IMAGE_MODEL_IDS;
95625
96037
  var DEFAULT_MAX_WAIT_MS2 = 24e4;
95626
96038
  registerSchema({
@@ -95756,7 +96168,7 @@ function buildGenerateBody(args8, prompt) {
95756
96168
  }
95757
96169
  return body;
95758
96170
  }
95759
- var generateCommand = defineCommand241({
96171
+ var generateCommand = defineCommand242({
95760
96172
  meta: {
95761
96173
  name: "generate",
95762
96174
  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 (default \u2014 fast, best at editing a reference and at extreme ratios), google/gemini-3-pro-image (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]]'"
@@ -95839,7 +96251,7 @@ var generateCommand = defineCommand241({
95839
96251
  });
95840
96252
 
95841
96253
  // src/commands/studio/get.ts
95842
- import { defineCommand as defineCommand242 } from "citty";
96254
+ import { defineCommand as defineCommand243 } from "citty";
95843
96255
  registerSchema({
95844
96256
  command: "studio.get",
95845
96257
  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.",
@@ -95848,7 +96260,7 @@ registerSchema({
95848
96260
  full: { type: "boolean", description: "Include settings, references and attribution", required: false }
95849
96261
  }
95850
96262
  });
95851
- var getCommand5 = defineCommand242({
96263
+ var getCommand5 = defineCommand243({
95852
96264
  meta: {
95853
96265
  name: "get",
95854
96266
  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"
@@ -95877,7 +96289,7 @@ var getCommand5 = defineCommand242({
95877
96289
  });
95878
96290
 
95879
96291
  // src/commands/studio/improve.ts
95880
- import { defineCommand as defineCommand243 } from "citty";
96292
+ import { defineCommand as defineCommand244 } from "citty";
95881
96293
  var DESCRIPTION7 = "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.";
95882
96294
  registerSchema({
95883
96295
  command: "studio.improve",
@@ -95902,7 +96314,7 @@ registerSchema({
95902
96314
  }
95903
96315
  }
95904
96316
  });
95905
- var improveCommand = defineCommand243({
96317
+ var improveCommand = defineCommand244({
95906
96318
  meta: {
95907
96319
  name: "improve",
95908
96320
  description: `${DESCRIPTION7}
@@ -95946,7 +96358,7 @@ Examples:
95946
96358
  });
95947
96359
 
95948
96360
  // src/commands/studio/keep.ts
95949
- import { defineCommand as defineCommand244 } from "citty";
96361
+ import { defineCommand as defineCommand245 } from "citty";
95950
96362
  registerSchema({
95951
96363
  command: "studio.keep",
95952
96364
  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.",
@@ -95956,7 +96368,7 @@ registerSchema({
95956
96368
  undo: { type: "boolean", description: "Un-star an image, or take a kept clip back out", required: false }
95957
96369
  }
95958
96370
  });
95959
- var keepCommand = defineCommand244({
96371
+ var keepCommand = defineCommand245({
95960
96372
  meta: {
95961
96373
  name: "keep",
95962
96374
  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"
@@ -96007,7 +96419,7 @@ var keepCommand = defineCommand244({
96007
96419
  });
96008
96420
 
96009
96421
  // src/commands/studio/list.ts
96010
- import { defineCommand as defineCommand245 } from "citty";
96422
+ import { defineCommand as defineCommand246 } from "citty";
96011
96423
  registerSchema({
96012
96424
  command: "studio.list",
96013
96425
  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.",
@@ -96018,7 +96430,7 @@ registerSchema({
96018
96430
  full: { type: "boolean", description: "Include settings, references and attribution", required: false }
96019
96431
  }
96020
96432
  });
96021
- var listCommand20 = defineCommand245({
96433
+ var listCommand20 = defineCommand246({
96022
96434
  meta: {
96023
96435
  name: "list",
96024
96436
  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"
@@ -96052,7 +96464,7 @@ var listCommand20 = defineCommand245({
96052
96464
  });
96053
96465
 
96054
96466
  // src/commands/studio/models.ts
96055
- import { defineCommand as defineCommand246 } from "citty";
96467
+ import { defineCommand as defineCommand247 } from "citty";
96056
96468
  var DESCRIPTION8 = "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.";
96057
96469
  registerSchema({
96058
96470
  command: "studio.models",
@@ -96139,7 +96551,7 @@ function buildModelCards(kind, model) {
96139
96551
  const selected = model ? ids2.filter((id) => id === model) : ids2;
96140
96552
  return selected.map((id) => build(id));
96141
96553
  }
96142
- var modelsCommand = defineCommand246({
96554
+ var modelsCommand = defineCommand247({
96143
96555
  meta: {
96144
96556
  name: "models",
96145
96557
  description: `${DESCRIPTION8}
@@ -96186,13 +96598,13 @@ Examples:
96186
96598
  });
96187
96599
 
96188
96600
  // src/commands/studio/skills.ts
96189
- import { defineCommand as defineCommand247 } from "citty";
96601
+ import { defineCommand as defineCommand248 } from "citty";
96190
96602
  registerSchema({
96191
96603
  command: "studio.skills",
96192
96604
  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.",
96193
96605
  args: {}
96194
96606
  });
96195
- var skillsCommand = defineCommand247({
96607
+ var skillsCommand = defineCommand248({
96196
96608
  meta: {
96197
96609
  name: "skills",
96198
96610
  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"
@@ -96216,7 +96628,7 @@ var skillsCommand = defineCommand247({
96216
96628
  });
96217
96629
 
96218
96630
  // src/commands/studio/index.ts
96219
- var studioCommand = defineCommand248({
96631
+ var studioCommand = defineCommand249({
96220
96632
  meta: {
96221
96633
  name: "studio",
96222
96634
  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.
@@ -96259,14 +96671,14 @@ Full guide: __tooling__/docs/tools/baker/studio.md`
96259
96671
  });
96260
96672
 
96261
96673
  // src/commands/tag-manager/index.ts
96262
- import { defineCommand as defineCommand252 } from "citty";
96674
+ import { defineCommand as defineCommand253 } from "citty";
96263
96675
 
96264
96676
  // src/commands/tag-manager/draft.ts
96265
- import { defineCommand as defineCommand249 } from "citty";
96677
+ import { defineCommand as defineCommand250 } from "citty";
96266
96678
 
96267
96679
  // src/commands/tag-manager/shared.ts
96268
96680
  import { readFileSync as readFileSync23 } from "fs";
96269
- function failValidation5(message) {
96681
+ function failValidation6(message) {
96270
96682
  writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
96271
96683
  process.exit(1);
96272
96684
  }
@@ -96274,7 +96686,7 @@ function requireTarget7(args8, entity) {
96274
96686
  const positional = Array.isArray(args8._) ? args8._[0] : void 0;
96275
96687
  const target2 = args8.id ?? args8.target ?? positional;
96276
96688
  if (typeof target2 !== "string" || target2.length === 0) {
96277
- failValidation5(`pass the ${entity} id or path as the positional argument`);
96689
+ failValidation6(`pass the ${entity} id or path as the positional argument`);
96278
96690
  }
96279
96691
  return target2;
96280
96692
  }
@@ -96283,16 +96695,16 @@ function loadJsonArg2(args8, flag = "json") {
96283
96695
  const file = args8.file;
96284
96696
  const raw = typeof file === "string" && file.length > 0 ? readFileSync23(file, "utf8") : typeof inline === "string" && inline.length > 0 ? inline : void 0;
96285
96697
  if (raw === void 0) {
96286
- failValidation5(`pass --${flag} with inline JSON or --file with a path to a JSON file`);
96698
+ failValidation6(`pass --${flag} with inline JSON or --file with a path to a JSON file`);
96287
96699
  }
96288
96700
  try {
96289
96701
  const parsed = JSON.parse(raw);
96290
96702
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
96291
- failValidation5(`--${flag} must be a JSON object`);
96703
+ failValidation6(`--${flag} must be a JSON object`);
96292
96704
  }
96293
96705
  return parsed;
96294
96706
  } catch {
96295
- return failValidation5(`--${flag} is not valid JSON`);
96707
+ return failValidation6(`--${flag} is not valid JSON`);
96296
96708
  }
96297
96709
  }
96298
96710
  var RETRYABLE_CODES2 = /* @__PURE__ */ new Set(["RATE_LIMITED", "UNAVAILABLE", "INTERNAL_ERROR", "NETWORK_ERROR", "TIMEOUT"]);
@@ -96388,13 +96800,13 @@ registerSchema({
96388
96800
  chat: { type: "string", description: CHAT_READ_ARG.description, required: false }
96389
96801
  }
96390
96802
  });
96391
- var draftCommand6 = defineCommand249({
96803
+ var draftCommand6 = defineCommand250({
96392
96804
  meta: {
96393
96805
  name: "draft",
96394
96806
  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."
96395
96807
  },
96396
96808
  subCommands: {
96397
- list: defineCommand249({
96809
+ list: defineCommand250({
96398
96810
  meta: {
96399
96811
  name: "list",
96400
96812
  description: "Review everything staged on this chat (--json for the raw envelope)"
@@ -96407,7 +96819,7 @@ var draftCommand6 = defineCommand249({
96407
96819
  await draftList2(args8.json === true, args8.chat);
96408
96820
  }
96409
96821
  }),
96410
- show: defineCommand249({
96822
+ show: defineCommand250({
96411
96823
  meta: {
96412
96824
  name: "show",
96413
96825
  description: "Print the full staged payload for one change \u2014 the receipt to verify it looks right before publish (never truncated)."
@@ -96424,7 +96836,7 @@ var draftCommand6 = defineCommand249({
96424
96836
  );
96425
96837
  }
96426
96838
  }),
96427
- amend: defineCommand249({
96839
+ amend: defineCommand250({
96428
96840
  meta: {
96429
96841
  name: "amend",
96430
96842
  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."
@@ -96441,7 +96853,7 @@ var draftCommand6 = defineCommand249({
96441
96853
  });
96442
96854
  }
96443
96855
  }),
96444
- remove: defineCommand249({
96856
+ remove: defineCommand250({
96445
96857
  meta: { name: "remove", description: "Remove one staged change (cascades to anything depending on it)" },
96446
96858
  args: { ref: { type: "positional", description: "Staged ref (gtm_temp_*) or target", required: false } },
96447
96859
  run: async ({ args: args8 }) => {
@@ -96450,7 +96862,7 @@ var draftCommand6 = defineCommand249({
96450
96862
  });
96451
96863
  }
96452
96864
  }),
96453
- clear: defineCommand249({
96865
+ clear: defineCommand250({
96454
96866
  meta: { name: "clear", description: "Discard all Tag Manager changes staged on this chat" },
96455
96867
  run: async () => {
96456
96868
  await draftAction3("/api/tag-manager/draft/clear", {});
@@ -96460,7 +96872,7 @@ var draftCommand6 = defineCommand249({
96460
96872
  });
96461
96873
 
96462
96874
  // src/commands/tag-manager/read.ts
96463
- import { defineCommand as defineCommand250 } from "citty";
96875
+ import { defineCommand as defineCommand251 } from "citty";
96464
96876
  registerSchema({
96465
96877
  command: "tagManager.containers",
96466
96878
  description: "List the Google Tag Manager containers this company selected. There can be several, and Baker may read and change all of them. Start here to confirm which containers you are managing.",
@@ -96508,7 +96920,7 @@ function containersHints(containers, unavailableContainers = []) {
96508
96920
  }
96509
96921
  return hints2;
96510
96922
  }
96511
- var containersCommand = defineCommand250({
96923
+ var containersCommand = defineCommand251({
96512
96924
  meta: {
96513
96925
  name: "containers",
96514
96926
  description: `List Tag Manager containers selected by this company.
@@ -96525,7 +96937,7 @@ Start here:
96525
96937
  }
96526
96938
  }
96527
96939
  });
96528
- var readCommand = defineCommand250({
96940
+ var readCommand = defineCommand251({
96529
96941
  meta: {
96530
96942
  name: "read",
96531
96943
  description: `Read the current contents of the Tag Manager container \u2014 always do this before staging changes.
@@ -96567,7 +96979,7 @@ Examples:
96567
96979
  });
96568
96980
 
96569
96981
  // src/commands/tag-manager/write-commands.ts
96570
- import { defineCommand as defineCommand251 } from "citty";
96982
+ import { defineCommand as defineCommand252 } from "citty";
96571
96983
  var CONTAINER_ARG_DESCRIPTION = "Numeric container id (optional only when one container is connected \u2014 run `baker tag-manager containers`)";
96572
96984
  var ENTITIES = [
96573
96985
  {
@@ -96623,10 +97035,10 @@ for (const { entity, noun, createHint } of ENTITIES) {
96623
97035
  });
96624
97036
  }
96625
97037
  function entityCommand(entity, noun, example) {
96626
- return defineCommand251({
97038
+ return defineCommand252({
96627
97039
  meta: { name: entity, description: `Stage ${noun} changes on this chat's Tag Manager draft` },
96628
97040
  subCommands: {
96629
- create: defineCommand251({
97041
+ create: defineCommand252({
96630
97042
  meta: {
96631
97043
  name: "create",
96632
97044
  description: `Stage a new ${noun}
@@ -96648,7 +97060,7 @@ Examples:
96648
97060
  });
96649
97061
  }
96650
97062
  }),
96651
- update: defineCommand251({
97063
+ update: defineCommand252({
96652
97064
  meta: {
96653
97065
  name: "update",
96654
97066
  description: `Stage an update to an existing ${noun} (pass its id or path)`
@@ -96668,7 +97080,7 @@ Examples:
96668
97080
  });
96669
97081
  }
96670
97082
  }),
96671
- delete: defineCommand251({
97083
+ delete: defineCommand252({
96672
97084
  meta: { name: "delete", description: `Stage the deletion of a ${noun} (pass its id or path)` },
96673
97085
  args: {
96674
97086
  id: { type: "positional", description: `${noun} id or path`, required: false },
@@ -96709,7 +97121,7 @@ function builtinTypes(args8) {
96709
97121
  }
96710
97122
  return raw.split(",").map((entry) => entry.trim());
96711
97123
  }
96712
- var builtinCommand = defineCommand251({
97124
+ var builtinCommand = defineCommand252({
96713
97125
  meta: {
96714
97126
  name: "builtin",
96715
97127
  description: `Enable or disable built-in variables
@@ -96719,7 +97131,7 @@ Examples:
96719
97131
  baker tag-manager builtin disable --types formId`
96720
97132
  },
96721
97133
  subCommands: {
96722
- enable: defineCommand251({
97134
+ enable: defineCommand252({
96723
97135
  meta: { name: "enable", description: "Stage enabling built-in variables" },
96724
97136
  args: {
96725
97137
  types: { type: "string", description: "Comma-separated types", required: false },
@@ -96733,7 +97145,7 @@ Examples:
96733
97145
  });
96734
97146
  }
96735
97147
  }),
96736
- disable: defineCommand251({
97148
+ disable: defineCommand252({
96737
97149
  meta: { name: "disable", description: "Stage disabling built-in variables" },
96738
97150
  args: {
96739
97151
  types: { type: "string", description: "Comma-separated types", required: false },
@@ -96751,7 +97163,7 @@ Examples:
96751
97163
  });
96752
97164
 
96753
97165
  // src/commands/tag-manager/index.ts
96754
- var tagManagerCommand = defineCommand252({
97166
+ var tagManagerCommand = defineCommand253({
96755
97167
  meta: {
96756
97168
  name: "tag-manager",
96757
97169
  description: `Read and change what lives inside the client's Google Tag Manager container \u2014 tags, triggers, variables, folders and built-in variables.
@@ -96788,7 +97200,7 @@ Full guide: __tooling__/docs/tools/baker/tag-manager.md`
96788
97200
  });
96789
97201
 
96790
97202
  // src/commands/tags/index.ts
96791
- import { defineCommand as defineCommand253 } from "citty";
97203
+ import { defineCommand as defineCommand254 } from "citty";
96792
97204
 
96793
97205
  // src/commands/tags/shared.ts
96794
97206
  function failApi3(err) {
@@ -96868,7 +97280,7 @@ async function listTags(json) {
96868
97280
  var listArgs12 = {
96869
97281
  json: { type: "boolean", description: "Print the raw JSON envelope instead of the readable list" }
96870
97282
  };
96871
- var listCommand21 = defineCommand253({
97283
+ var listCommand21 = defineCommand254({
96872
97284
  meta: {
96873
97285
  name: "list",
96874
97286
  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"
@@ -96887,7 +97299,7 @@ async function listDraft4(chat) {
96887
97299
  failApi3(err);
96888
97300
  }
96889
97301
  }
96890
- var draftCommand7 = defineCommand253({
97302
+ var draftCommand7 = defineCommand254({
96891
97303
  meta: {
96892
97304
  name: "draft",
96893
97305
  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."
@@ -96897,7 +97309,7 @@ var draftCommand7 = defineCommand253({
96897
97309
  await listDraft4(args8.chat);
96898
97310
  }
96899
97311
  });
96900
- var tagsCommand4 = defineCommand253({
97312
+ var tagsCommand4 = defineCommand254({
96901
97313
  meta: {
96902
97314
  name: "tags",
96903
97315
  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.
@@ -96926,10 +97338,10 @@ Full guide: __tooling__/docs/tools/baker/tags.md`
96926
97338
  });
96927
97339
 
96928
97340
  // src/commands/testimonials/index.ts
96929
- import { defineCommand as defineCommand257 } from "citty";
97341
+ import { defineCommand as defineCommand258 } from "citty";
96930
97342
 
96931
97343
  // src/commands/testimonials/get.ts
96932
- import { defineCommand as defineCommand254 } from "citty";
97344
+ import { defineCommand as defineCommand255 } from "citty";
96933
97345
  registerSchema({
96934
97346
  command: "testimonials.get",
96935
97347
  description: "Get a single testimonial by ID",
@@ -96937,7 +97349,7 @@ registerSchema({
96937
97349
  id: { type: "string", description: "Testimonial ID", required: true }
96938
97350
  }
96939
97351
  });
96940
- var getCommand6 = defineCommand254({
97352
+ var getCommand6 = defineCommand255({
96941
97353
  meta: { name: "get", description: "Get a single testimonial by ID. Example: baker testimonials get j571abc123" },
96942
97354
  args: {
96943
97355
  id: { type: "positional", description: "Testimonial ID", required: false },
@@ -96974,7 +97386,7 @@ var getCommand6 = defineCommand254({
96974
97386
  });
96975
97387
 
96976
97388
  // src/commands/testimonials/list.ts
96977
- import { defineCommand as defineCommand255 } from "citty";
97389
+ import { defineCommand as defineCommand256 } from "citty";
96978
97390
 
96979
97391
  // src/commands/testimonials/emptyCorpusHints.ts
96980
97392
  function resolveEmptyReason({
@@ -97131,7 +97543,7 @@ function buildListParams(args8) {
97131
97543
  }
97132
97544
  return params;
97133
97545
  }
97134
- var listCommand22 = defineCommand255({
97546
+ var listCommand22 = defineCommand256({
97135
97547
  meta: {
97136
97548
  name: "list",
97137
97549
  description: "List testimonials with optional filters. Example: baker testimonials list --source google --sentiment positive"
@@ -97191,7 +97603,7 @@ var listCommand22 = defineCommand255({
97191
97603
  });
97192
97604
 
97193
97605
  // src/commands/testimonials/search.ts
97194
- import { defineCommand as defineCommand256 } from "citty";
97606
+ import { defineCommand as defineCommand257 } from "citty";
97195
97607
  var FILTER_FLAGS2 = ["source", "rating-min", "rating-max", "status", "sentiment", "language", "tags"];
97196
97608
  function languageBiasHint(results, requestedLanguage) {
97197
97609
  if (requestedLanguage) {
@@ -97270,7 +97682,7 @@ function buildSearchRequest(query, args8) {
97270
97682
  }
97271
97683
  return body;
97272
97684
  }
97273
- var searchCommand3 = defineCommand256({
97685
+ var searchCommand4 = defineCommand257({
97274
97686
  meta: {
97275
97687
  name: "search",
97276
97688
  description: "Semantic search testimonials by text query. Uses hybrid BM25 + vector + reranking. Example: baker testimonials search 'great service' --rating-min 4"
@@ -97334,7 +97746,7 @@ var searchCommand3 = defineCommand256({
97334
97746
  var tagsCommand5 = makeTagsCommand("testimonials", "testimonial", "/api/testimonials/tags");
97335
97747
 
97336
97748
  // src/commands/testimonials/index.ts
97337
- var testimonialsCommand = defineCommand257({
97749
+ var testimonialsCommand = defineCommand258({
97338
97750
  meta: {
97339
97751
  name: "testimonials",
97340
97752
  description: `Find and browse testimonials in Baker. Subcommands: search, get, list, tags.
@@ -97349,17 +97761,17 @@ Full guide: __tooling__/docs/tools/baker/testimonials.md`
97349
97761
  },
97350
97762
  subCommands: {
97351
97763
  get: getCommand6,
97352
- search: searchCommand3,
97764
+ search: searchCommand4,
97353
97765
  list: listCommand22,
97354
97766
  tags: tagsCommand5
97355
97767
  }
97356
97768
  });
97357
97769
 
97358
97770
  // src/commands/videos/index.ts
97359
- import { defineCommand as defineCommand266 } from "citty";
97771
+ import { defineCommand as defineCommand267 } from "citty";
97360
97772
 
97361
97773
  // src/commands/videos/delete.ts
97362
- import { defineCommand as defineCommand258 } from "citty";
97774
+ import { defineCommand as defineCommand259 } from "citty";
97363
97775
  registerSchema({
97364
97776
  command: "videos.delete",
97365
97777
  description: "Delete a video by ID",
@@ -97373,7 +97785,7 @@ registerSchema({
97373
97785
  }
97374
97786
  }
97375
97787
  });
97376
- var deleteCommand4 = defineCommand258({
97788
+ var deleteCommand4 = defineCommand259({
97377
97789
  meta: {
97378
97790
  name: "delete",
97379
97791
  description: "Delete a video by ID. Use --dry-run to preview. Example: baker videos delete j571abc123 --dry-run"
@@ -97414,7 +97826,7 @@ var deleteCommand4 = defineCommand258({
97414
97826
  });
97415
97827
 
97416
97828
  // src/commands/videos/describe.ts
97417
- import { defineCommand as defineCommand259 } from "citty";
97829
+ import { defineCommand as defineCommand260 } from "citty";
97418
97830
  var DESCRIPTION_HELP2 = "What the clip actually shows, in the words someone would search for it by. This is what `baker videos search` retrieves on.";
97419
97831
  registerSchema({
97420
97832
  command: "videos.describe",
@@ -97431,7 +97843,7 @@ registerSchema({
97431
97843
  full: { type: "boolean", description: "Return the whole library row, not just what changed", required: false }
97432
97844
  }
97433
97845
  });
97434
- var describeCommand2 = defineCommand259({
97846
+ var describeCommand2 = defineCommand260({
97435
97847
  meta: {
97436
97848
  name: "describe",
97437
97849
  description: `Correct what the library says a video is. Only the fields you pass change; the rest keep their current values.
@@ -97493,7 +97905,7 @@ import { constants } from "fs";
97493
97905
  import { copyFile as copyFile2, mkdir as mkdir17, mkdtemp as mkdtemp3, rm as rm10 } from "fs/promises";
97494
97906
  import { tmpdir as tmpdir4 } from "os";
97495
97907
  import path56 from "path";
97496
- import { defineCommand as defineCommand260 } from "citty";
97908
+ import { defineCommand as defineCommand261 } from "citty";
97497
97909
 
97498
97910
  // src/commands/videos/videoPlatforms.ts
97499
97911
  var PLATFORM_DOMAINS = {
@@ -97631,7 +98043,7 @@ function positiveInt(value2) {
97631
98043
  const parsed = typeof value2 === "string" ? Number.parseInt(value2, 10) : Number.NaN;
97632
98044
  return Number.isInteger(parsed) && parsed > 0 ? parsed : void 0;
97633
98045
  }
97634
- var fetchCommand2 = defineCommand260({
98046
+ var fetchCommand2 = defineCommand261({
97635
98047
  meta: {
97636
98048
  name: "fetch",
97637
98049
  description: "Download a video page to a local .mp4 without adding it to the library. Same downloader as `ingest` (proxy fallback, duration check).\n\nExample: baker videos fetch https://www.youtube.com/watch?v=abc123 --out ./clips"
@@ -97717,7 +98129,7 @@ async function placeInto(outDir, source, stem) {
97717
98129
  }
97718
98130
 
97719
98131
  // src/commands/videos/get.ts
97720
- import { defineCommand as defineCommand261 } from "citty";
98132
+ import { defineCommand as defineCommand262 } from "citty";
97721
98133
  registerSchema({
97722
98134
  command: "videos.get",
97723
98135
  description: "Get a single video by ID",
@@ -97725,7 +98137,7 @@ registerSchema({
97725
98137
  id: { type: "string", description: "Video ID", required: true }
97726
98138
  }
97727
98139
  });
97728
- var getCommand7 = defineCommand261({
98140
+ var getCommand7 = defineCommand262({
97729
98141
  meta: { name: "get", description: "Get a single video by ID. Example: baker videos get j571abc123" },
97730
98142
  args: {
97731
98143
  id: { type: "positional", description: "Video ID", required: false },
@@ -97762,7 +98174,7 @@ var getCommand7 = defineCommand261({
97762
98174
  });
97763
98175
 
97764
98176
  // src/commands/videos/group.ts
97765
- import { defineCommand as defineCommand262 } from "citty";
98177
+ import { defineCommand as defineCommand263 } from "citty";
97766
98178
  registerSchema({
97767
98179
  command: "videos.group",
97768
98180
  description: "List every clip and image that arrived in the same set as this video (carousel slides, one page)",
@@ -97771,7 +98183,7 @@ registerSchema({
97771
98183
  "group-key": { type: "string", description: "The set key directly, when you already have it", required: false }
97772
98184
  }
97773
98185
  });
97774
- var groupCommand2 = defineCommand262({
98186
+ var groupCommand2 = defineCommand263({
97775
98187
  meta: {
97776
98188
  name: "group",
97777
98189
  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>"
@@ -97794,7 +98206,7 @@ var groupCommand2 = defineCommand262({
97794
98206
  import { mkdtemp as mkdtemp4, rm as rm11, stat as stat12 } from "fs/promises";
97795
98207
  import { tmpdir as tmpdir5 } from "os";
97796
98208
  import path57 from "path";
97797
- import { defineCommand as defineCommand263 } from "citty";
98209
+ import { defineCommand as defineCommand264 } from "citty";
97798
98210
 
97799
98211
  // src/lib/streamUpload.ts
97800
98212
  import { createHash as createHash3 } from "crypto";
@@ -97847,7 +98259,7 @@ registerSchema({
97847
98259
  "dry-run": { type: "boolean", description: "Preview the operation without executing", required: false }
97848
98260
  }
97849
98261
  });
97850
- var ingestCommand2 = defineCommand263({
98262
+ var ingestCommand2 = defineCommand264({
97851
98263
  meta: {
97852
98264
  name: "ingest",
97853
98265
  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"
@@ -98109,7 +98521,7 @@ async function uploadToAssetStore(filePath, sizeBytes) {
98109
98521
  }
98110
98522
 
98111
98523
  // src/commands/videos/search.ts
98112
- import { defineCommand as defineCommand264 } from "citty";
98524
+ import { defineCommand as defineCommand265 } from "citty";
98113
98525
  registerSchema({
98114
98526
  command: "videos.search",
98115
98527
  description: "Search videos by text query. Only returns ready videos.",
@@ -98119,7 +98531,23 @@ registerSchema({
98119
98531
  tags: { type: "string", description: "Comma-separated tags to filter by", required: false }
98120
98532
  }
98121
98533
  });
98122
- var searchCommand4 = defineCommand264({
98534
+ function searchHints(results) {
98535
+ const hints2 = [];
98536
+ const parody = results.filter((r) => r.analysis?.tone === "parody").map((r) => r._id);
98537
+ if (parody.length > 0) {
98538
+ hints2.push(
98539
+ `tone: parody \u2014 ${parody.join(", ")}. These play the offer for laughs: fine for a social post, wrong as proof or on a landing page.`
98540
+ );
98541
+ }
98542
+ const unchecked = results.filter((r) => r.analysis?.tone === void 0).map((r) => r._id);
98543
+ if (unchecked.length > 0) {
98544
+ hints2.push(
98545
+ `No format/tone/productClaims yet \u2014 ${unchecked.join(", ")}. Before putting one on a page, read its transcript, scenes and people (\`baker videos get <id> --full\`) and judge it yourself: is it about the offer, is it sincere, and who is speaking?`
98546
+ );
98547
+ }
98548
+ return hints2.length > 0 ? hints2 : void 0;
98549
+ }
98550
+ var searchCommand5 = defineCommand265({
98123
98551
  meta: {
98124
98552
  name: "search",
98125
98553
  description: "Semantic search videos by text query. Uses hybrid BM25 + vector + reranking. Example: baker videos search 'product demo' --tags tutorial"
@@ -98148,7 +98576,7 @@ var searchCommand4 = defineCommand264({
98148
98576
  }
98149
98577
  const data = await apiPost("/api/videos/search", body);
98150
98578
  writeOutput(
98151
- { ok: true, data },
98579
+ { ok: true, data, hints: searchHints(data) },
98152
98580
  args8.output || "json",
98153
98581
  args8.fields ? args8.fields.split(",") : void 0,
98154
98582
  args8.full,
@@ -98171,7 +98599,7 @@ var tagsCommand6 = makeTagsCommand("videos", "video", "/api/videos/tags");
98171
98599
  // src/commands/videos/upload.ts
98172
98600
  import { stat as stat13 } from "fs/promises";
98173
98601
  import { basename as basename4 } from "path";
98174
- import { defineCommand as defineCommand265 } from "citty";
98602
+ import { defineCommand as defineCommand266 } from "citty";
98175
98603
  registerSchema({
98176
98604
  command: "videos.upload",
98177
98605
  description: "Upload a video to Baker \u2014 a local file (via Mux direct upload) or a remote http(s) URL.",
@@ -98198,7 +98626,7 @@ registerSchema({
98198
98626
  function isRemoteUrl3(value2) {
98199
98627
  return /^https?:\/\//i.test(value2);
98200
98628
  }
98201
- var uploadCommand2 = defineCommand265({
98629
+ var uploadCommand2 = defineCommand266({
98202
98630
  meta: {
98203
98631
  name: "upload",
98204
98632
  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"
@@ -98267,7 +98695,7 @@ var uploadCommand2 = defineCommand265({
98267
98695
  });
98268
98696
 
98269
98697
  // src/commands/videos/index.ts
98270
- var videosCommand = defineCommand266({
98698
+ var videosCommand = defineCommand267({
98271
98699
  meta: {
98272
98700
  name: "videos",
98273
98701
  description: `Find and manage videos in Baker. Subcommands: search, get, upload, ingest, fetch, describe, delete, tags.
@@ -98287,7 +98715,7 @@ Full guide: __tooling__/docs/tools/baker/videos.md`
98287
98715
  subCommands: {
98288
98716
  get: getCommand7,
98289
98717
  group: groupCommand2,
98290
- search: searchCommand4,
98718
+ search: searchCommand5,
98291
98719
  upload: uploadCommand2,
98292
98720
  ingest: ingestCommand2,
98293
98721
  fetch: fetchCommand2,
@@ -98298,10 +98726,10 @@ Full guide: __tooling__/docs/tools/baker/videos.md`
98298
98726
  });
98299
98727
 
98300
98728
  // src/commands/winning-ads/index.ts
98301
- import { defineCommand as defineCommand277 } from "citty";
98729
+ import { defineCommand as defineCommand278 } from "citty";
98302
98730
 
98303
98731
  // src/commands/winning-ads/advertisers.ts
98304
- import { defineCommand as defineCommand267 } from "citty";
98732
+ import { defineCommand as defineCommand268 } from "citty";
98305
98733
 
98306
98734
  // src/commands/winning-ads/shared.ts
98307
98735
  function splitList4(value2) {
@@ -98354,7 +98782,7 @@ function advertiserNormalizer(record, full) {
98354
98782
  last_synced_at: record.last_synced_at ?? null
98355
98783
  };
98356
98784
  }
98357
- var advertisersCommand2 = defineCommand267({
98785
+ var advertisersCommand2 = defineCommand268({
98358
98786
  meta: {
98359
98787
  name: "advertisers",
98360
98788
  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'
@@ -98412,7 +98840,7 @@ var advertisersCommand2 = defineCommand267({
98412
98840
  });
98413
98841
 
98414
98842
  // src/commands/winning-ads/content.ts
98415
- import { defineCommand as defineCommand268 } from "citty";
98843
+ import { defineCommand as defineCommand269 } from "citty";
98416
98844
  registerSchema({
98417
98845
  command: "winning-ads.content",
98418
98846
  description: "Read what's INSIDE one winning ad: the spoken transcript, the on-screen text, and the ad copy. Use this after `search`/`winners`/`feed` return a shortlist \u2014 pass an ad_id to understand a reference before reproducing it. Static and carousel ads return the text drawn into the image with the job each piece does (headline, benefit, cta\u2026), the card it sits on, and their format_pattern. Add --full for how it is built (composition, colours, CTA, markers), how it persuades (awareness, mechanism, pains, desires, proof, objections), what it offers (offer type, price, guarantee, urgency), plus speech, pacing and soundtrack for video.",
@@ -98425,7 +98853,7 @@ registerSchema({
98425
98853
  }
98426
98854
  }
98427
98855
  });
98428
- var contentCommand = defineCommand268({
98856
+ var contentCommand = defineCommand269({
98429
98857
  meta: {
98430
98858
  name: "content",
98431
98859
  description: "Read the transcript + on-screen text + copy of one winning ad; --full adds its build, persuasion and offer. Example: baker winning-ads content adg_123 --platform meta --full --output md"
@@ -98474,7 +98902,7 @@ var contentCommand = defineCommand268({
98474
98902
  });
98475
98903
 
98476
98904
  // src/commands/winning-ads/feed.ts
98477
- import { defineCommand as defineCommand269 } from "citty";
98905
+ import { defineCommand as defineCommand270 } from "citty";
98478
98906
  function buildFeedParams(input) {
98479
98907
  const params = {};
98480
98908
  const advertiser = splitList4(input.advertiser);
@@ -98526,7 +98954,7 @@ registerSchema({
98526
98954
  format: { type: "string", description: "Comma-separated formats to include (e.g. static,video)", required: false }
98527
98955
  }
98528
98956
  });
98529
- var feedCommand2 = defineCommand269({
98957
+ var feedCommand2 = defineCommand270({
98530
98958
  meta: {
98531
98959
  name: "feed",
98532
98960
  description: "Winners across every brand you follow (browse, then trim per advertiser). Example: baker winning-ads feed --per-advertiser 5 --output md"
@@ -98611,7 +99039,7 @@ var feedCommand2 = defineCommand269({
98611
99039
  });
98612
99040
 
98613
99041
  // src/commands/winning-ads/follow.ts
98614
- import { defineCommand as defineCommand270 } from "citty";
99042
+ import { defineCommand as defineCommand271 } from "citty";
98615
99043
  var PLATFORMS = adLibraryPlatformSchema.options;
98616
99044
  registerSchema({
98617
99045
  command: "winning-ads.follow",
@@ -98626,7 +99054,7 @@ registerSchema({
98626
99054
  label: { type: "string", description: "Optional display label (defaults to the resolved name)", required: false }
98627
99055
  }
98628
99056
  });
98629
- var followCommand = defineCommand270({
99057
+ var followCommand = defineCommand271({
98630
99058
  meta: {
98631
99059
  name: "follow",
98632
99060
  description: 'Follow a brand to track ALL its ads \u2014 every platform and country. --platform is how we read your input, not a limit. A domain tracks every platform we can resolve. Example: baker winning-ads follow "deel.com" --platform meta'
@@ -98673,7 +99101,7 @@ var followCommand = defineCommand270({
98673
99101
  });
98674
99102
 
98675
99103
  // src/commands/winning-ads/follow-competitors.ts
98676
- import { defineCommand as defineCommand271 } from "citty";
99104
+ import { defineCommand as defineCommand272 } from "citty";
98677
99105
  var PLATFORMS2 = adLibraryPlatformSchema.options;
98678
99106
  var BATCH_TIMEOUT_MS = 3e5;
98679
99107
  function buildFollowBatchBody(input) {
@@ -98706,7 +99134,7 @@ registerSchema({
98706
99134
  }
98707
99135
  }
98708
99136
  });
98709
- var followCompetitorsCommand = defineCommand271({
99137
+ var followCompetitorsCommand = defineCommand272({
98710
99138
  meta: {
98711
99139
  name: "follow-competitors",
98712
99140
  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"'
@@ -98781,7 +99209,7 @@ var followCompetitorsCommand = defineCommand271({
98781
99209
  });
98782
99210
 
98783
99211
  // src/commands/winning-ads/following.ts
98784
- import { defineCommand as defineCommand272 } from "citty";
99212
+ import { defineCommand as defineCommand273 } from "citty";
98785
99213
  registerSchema({
98786
99214
  command: "winning-ads.following",
98787
99215
  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.",
@@ -98835,7 +99263,7 @@ function followingNormalizer(record, full) {
98835
99263
  platforms: Array.isArray(record.platforms) ? record.platforms : []
98836
99264
  };
98837
99265
  }
98838
- var followingCommand = defineCommand272({
99266
+ var followingCommand = defineCommand273({
98839
99267
  meta: {
98840
99268
  name: "following",
98841
99269
  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"
@@ -98871,7 +99299,7 @@ var followingCommand = defineCommand272({
98871
99299
  });
98872
99300
 
98873
99301
  // src/commands/winning-ads/search.ts
98874
- import { defineCommand as defineCommand273 } from "citty";
99302
+ import { defineCommand as defineCommand274 } from "citty";
98875
99303
  registerSchema({
98876
99304
  command: "winning-ads.search",
98877
99305
  description: "Search the ad-dna corpus of scored winning ads, the company's followed brands first. Returns a lean shortlist (advertiser, summary, scores, days_active/active/winner_category as proof, media_url, and visual/container/mechanism when known) to pick a reference to reproduce. Each search is a paid reranker + judge call: one strong search, at most one adjusted retry. A value outside a closed list (--platform, --format, --winner-category, --awareness, --format-pattern, --sort) is refused with error.fix.validValues.",
@@ -99012,7 +99440,7 @@ function buildSearchBody2(args8) {
99012
99440
  }
99013
99441
  return body;
99014
99442
  }
99015
- function searchHints(data) {
99443
+ function searchHints2(data) {
99016
99444
  const hints2 = [];
99017
99445
  if ((data.following_count ?? 0) === 0) {
99018
99446
  hints2.push(
@@ -99127,7 +99555,7 @@ function searchRejection(args8, body) {
99127
99555
  }
99128
99556
  function writeSearchResult(data, opts) {
99129
99557
  const rawResults = Array.isArray(data?.results) ? data.results : [];
99130
- const hints2 = searchHints(data ?? {});
99558
+ const hints2 = searchHints2(data ?? {});
99131
99559
  const withHints2 = hints2.length > 0 ? { hints: hints2 } : {};
99132
99560
  if (opts.output === "json") {
99133
99561
  const results = rawResults.map((r) => winningAdNormalizer(r, opts.full));
@@ -99149,7 +99577,7 @@ function writeSearchResult(data, opts) {
99149
99577
  writeOutput({ ok: true, data: rawResults, ...withHints2 }, opts.output, opts.fields, opts.full, winningAdNormalizer);
99150
99578
  }
99151
99579
  var SEARCH_TIMEOUT_MS = 12e4;
99152
- var searchCommand5 = defineCommand273({
99580
+ var searchCommand6 = defineCommand274({
99153
99581
  meta: {
99154
99582
  name: "search",
99155
99583
  description: "Search winning reference ads, the company's followed brands first. Start here: baker winning-ads search 'B2B SaaS before/after AI automation' --platform meta --format static --exclude-advertiser adv_123 --output md. A set of references for N concepts in one call: add --diverse --limit N. The best proven ad in one container from any category: --format-pattern comparison_table (no query needed; add --sort longest_running to order by staying power). Each search is a paid reranker + judge call: one strong search, at most one adjusted retry."
@@ -99275,7 +99703,7 @@ var searchCommand5 = defineCommand273({
99275
99703
  });
99276
99704
 
99277
99705
  // src/commands/winning-ads/seeds.ts
99278
- import { defineCommand as defineCommand274 } from "citty";
99706
+ import { defineCommand as defineCommand275 } from "citty";
99279
99707
  function leanRow(r) {
99280
99708
  return {
99281
99709
  key: r.key,
@@ -99303,7 +99731,7 @@ function makeSeedCommand(opts) {
99303
99731
  limit: { type: "number", description: "Max keys 1-100 (default 20)", required: false, default: 20 }
99304
99732
  }
99305
99733
  });
99306
- return defineCommand274({
99734
+ return defineCommand275({
99307
99735
  meta: { name: opts.name, description: opts.description },
99308
99736
  args: {
99309
99737
  platform: { type: "string", description: "Single platform to segment on", required: false },
@@ -99352,7 +99780,7 @@ var formatsCommand = makeSeedCommand({
99352
99780
  });
99353
99781
 
99354
99782
  // src/commands/winning-ads/unfollow.ts
99355
- import { defineCommand as defineCommand275 } from "citty";
99783
+ import { defineCommand as defineCommand276 } from "citty";
99356
99784
  registerSchema({
99357
99785
  command: "winning-ads.unfollow",
99358
99786
  description: "Stop following a brand \u2014 removes it from your ad-dna library by advertiser id.",
@@ -99360,7 +99788,7 @@ registerSchema({
99360
99788
  advertiser: { type: "string", description: "Advertiser id to unfollow", required: true }
99361
99789
  }
99362
99790
  });
99363
- var unfollowCommand = defineCommand275({
99791
+ var unfollowCommand = defineCommand276({
99364
99792
  meta: {
99365
99793
  name: "unfollow",
99366
99794
  description: "Stop following a brand by advertiser id. Example: baker winning-ads unfollow adv_123"
@@ -99381,7 +99809,7 @@ var unfollowCommand = defineCommand275({
99381
99809
  });
99382
99810
 
99383
99811
  // src/commands/winning-ads/winners.ts
99384
- import { defineCommand as defineCommand276 } from "citty";
99812
+ import { defineCommand as defineCommand277 } from "citty";
99385
99813
  registerSchema({
99386
99814
  command: "winning-ads.winners",
99387
99815
  description: "Top winning ads for one advertiser id (from `advertisers` or `following`). Returns lean winner cards; add --full for DNA + longevity.",
@@ -99391,7 +99819,7 @@ registerSchema({
99391
99819
  platform: { type: "string", description: "Filter to a single platform: meta|linkedin|tiktok", required: false }
99392
99820
  }
99393
99821
  });
99394
- var winnersCommand = defineCommand276({
99822
+ var winnersCommand = defineCommand277({
99395
99823
  meta: {
99396
99824
  name: "winners",
99397
99825
  description: "Top winning ads for a specific advertiser id. Example: baker winning-ads winners adv_123 --top 15 --output md"
@@ -99441,7 +99869,7 @@ var winnersCommand = defineCommand276({
99441
99869
  });
99442
99870
 
99443
99871
  // src/commands/winning-ads/index.ts
99444
- var winningAdsCommand = defineCommand277({
99872
+ var winningAdsCommand = defineCommand278({
99445
99873
  meta: {
99446
99874
  name: "winning-ads",
99447
99875
  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.
@@ -99477,7 +99905,7 @@ Examples:
99477
99905
  Full guide: __tooling__/docs/tools/baker/winning-ads.md`
99478
99906
  },
99479
99907
  subCommands: {
99480
- search: searchCommand5,
99908
+ search: searchCommand6,
99481
99909
  advertisers: advertisersCommand2,
99482
99910
  follow: followCommand,
99483
99911
  "follow-competitors": followCompetitorsCommand,
@@ -99509,7 +99937,7 @@ function getCliVersion() {
99509
99937
  }
99510
99938
 
99511
99939
  // src/cli.ts
99512
- var main = defineCommand278({
99940
+ var main = defineCommand279({
99513
99941
  meta: {
99514
99942
  name: "baker",
99515
99943
  version: getCliVersion(),